What is the Java protected keyword? I’m sure you’ve been asked this before, and the most common answer is something like: protected members are visible only within the same package and to subclasses in other packages.
That sounds right, but there is another part of this definition that we almost always ignore, and it matters because it affects how we structure our code. The next time you want to throw another protected on a method in a base class, you should probably have that in mind first.
In this post we look at that missing rule, and how it affects the way you design base classes and extension points in Java.
Protected in a single package
Let us start by designing a base PaymentProcessor. We expect other parts of the system to build on this base class, so we put some shared checks in there that we do not want to duplicate everywhere.
package com.heappulse.post3.payments;
public abstract class PaymentProcessor {
protected void check(PaymentRequest request) {
// Performs some checks
}
}
The intent here is simple, check centralizes the common validation that all processors should apply before doing work. As long as we stay in the same package, it is completely fine to call this protected method from another class:
package com.heappulse.post3.payments;
public final class PaymentMonitor {
public void inspect(PaymentProcessor processor,
PaymentRequest request) {
processor.check(request); // ok: same package
}
}
When the subclass moves to another package
So far, everything is in com.heappulse.post3.payments, and check behaves exactly like we expect. Now let us move to a different package, where an application wants to wrap a PaymentProcessor and reuse the same check logic.
package com.heappulse.post3.payments.processing;
import com.heappulse.post3.payments.PaymentProcessor;
import com.heappulse.post3.payments.PaymentRequest;
public class RetryablePaymentProcessor extends PaymentProcessor {
public void handle(PaymentProcessor delegate,
PaymentRequest request) {
delegate.check(request); // does NOT compile
// do work
}
}
The idea here is simple: RetryablePaymentProcessor wants to take any PaymentProcessor implementation, run the shared check on that delegate, and then do extra work (retries, logging, whatever).
From our usual understanding of protected this looks fine since RetryablePaymentProcessor extends PaymentProcessor, but if we try compiling the code , it fails with this error:
check(com.heappulse.post3.payments.PaymentRequest) has protected access in com.heappulse.post3.payments.PaymentProcessor
This is where the missing part of our definition comes in: in a different package, a protected member is only accessible from a subclass, and only through this, super, or a reference whose compile-time type is that subclass (or a subclass of it).
To fix this we can do this instead:
package com.heappulse.post3.payments.processing;
import com.heappulse.post3.payments.PaymentProcessor;
import com.heappulse.post3.payments.PaymentRequest;
public class RetryablePaymentProcessor extends PaymentProcessor {
public void handle(PaymentProcessor delegate,
PaymentRequest request) {
check(request);
// this.check(request); -> also works
// super.check(request); -> also works
// do work
}
}
What this means in practice
If your intention was to pass other processors into RetryablePaymentProcessor and let it reuse the shared check on that delegate before doing its own work, and have it call whatever check implementation that specific processor provides (for example another processor overrides check with extra checks), Java’s protected rule across packages blocks that. From here you are only allowed to call check on this, not on an arbitrary PaymentProcessor reference.
Also, if you really want that shared check to run on any PaymentProcessor you pass in, protected is the wrong tool once you cross packages. You either make that behaviour public or pull the logic into a small helper that everyone calls directly. In practice it is simple: keep protected for what a class and its subclasses do on themselves, otherwise use public or helper methods.
Conclusion
In this blog post we exposed the other half of Java protected and how it affects the way you design your base classes. In one package it behaves like we all expect, but once you cross packages the behavior changes. So protected access allows any class in the same package to call the method, but in another package you can only call it from a subclass on this (or super).



