Java Protected vs Default: Choosing the Right Access Modifier
java protected vs default: Understand the practical differences between Java's protected and default access modifiers, and learn when to use each for clean encapsulati...
java protected vs default requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
When you declare a member in Java, the choice between protected and default (package-private) access determines how much of your class's internal structure is exposed to subclasses and to other classes in the same package. The difference is subtle but has long-term consequences for API design and inheritance. This article explains exactly how protected and default access behave, where they overlap, and how to decide between them in real code.
The Core Difference Between Protected and Default
The default access modifier, often called package-private, restricts a member to classes within the same package. No subclass outside the package can access it, even if it inherits from the declaring class. The protected modifier goes one step further: it allows access from the same package and also from subclasses that live in different packages. In other words, protected is strictly less restrictive than default because it adds cross-package inheritance access while retaining all the package-level access.
package com.example.base; public class Base { int packagePrivateField = 1; protected int protectedField = 2; }
A class in the same package can read both fields. A subclass in a different package can read only protectedField. A non-subclass in a different package cannot read either.
How Default Access Works in Practice
Default access is the natural choice for internal implementation details that should not leak outside the package. It is common in large packages where multiple classes cooperate closely, such as utility classes, helper components, or internal data structures. Because the access is limited to the package, you can change the implementation without affecting code outside the package.
package com.example.internal; class Cache { void store(String key, Object value) { // implementation } } public class Service { private Cache cache = new Cache(); public void save(String key, Object value) { cache.store(key, value); } }
Here Cache is package-private, so it is invisible to the outside world. The Service class exposes a public method but keeps the cache implementation hidden. This is a classic use of default access for encapsulation.
How Protected Access Works with Inheritance
protected is designed for members that subclasses need to reuse or override, even when those subclasses are in different packages. It is a bridge between public API and private implementation. A common pattern is to define a template method in a base class and let subclasses override a protected hook.
package com.example.framework; public abstract class BaseProcessor { public final void process() { before(); doWork(); after(); } protected void before() {} protected abstract void doWork(); protected void after() {} }
A subclass in another package can override before() and after() because they are protected. If these methods were default access, the subclass would not be able to override them, breaking the framework's extensibility.
Comparing Protected and Default in the Same Package
Within the same package, protected and default behave identically. Both allow access from any class in that package. The difference only appears when a subclass is defined in a different package. This overlap often causes confusion because developers may not notice the distinction until they try to access a member from a subclass in another package.
| Access level | Same package | Subclass in different package | Any class in different package |
|---|---|---|---|
| default | Yes | No | No |
| protected | Yes | Yes | No |
The table shows that protected is a superset of default access. If you are designing a package that will be extended by external code, you need protected. If you are only organizing code within a single package, default is sufficient and more restrictive.
Choosing Between Protected and Default for API Design
The decision often comes down to whether you intend the member to be part of the extension contract. If a member is meant to be overridden or used by subclasses that you do not control, make it protected. If it is an internal helper that should not be visible to subclasses outside the package, keep it default.
Consider a library that provides a base class. If you expose a protected method, you are committing to its behavior and signature as part of the public API for subclasses. Changing it later can break external subclasses. Default access gives you more freedom to refactor because external subclasses cannot depend on it. This is a key maintainability tradeoff: protected increases extensibility but also increases the surface area you must keep stable.
Maintainability and Encapsulation Tradeoffs
Using default access aggressively reduces the risk of unintended coupling. External code cannot accidentally depend on internal members, so you can change them freely. However, default access can also hinder legitimate extension. If a subclass needs to customize behavior but cannot access the relevant method, you may be forced to make it public, which is even worse.
A balanced approach is to start with default access and promote a member to protected only when a concrete need arises. This keeps the API surface minimal and avoids premature exposure. When you do make a member protected, document its intended use so that subclasses know what is safe to override.
Common Mistakes When Using Protected and Default
One frequent mistake is assuming that protected also grants access to non-subclasses in other packages. It does not. A class that merely calls a protected method on an instance of a superclass from another package will fail to compile unless the calling class is a subclass and the access is through an instance of that subclass or a further subtype.
Another mistake is using protected for members that are not meant to be overridden. This exposes implementation details unnecessarily and makes future changes harder. Similarly, using default access for a method that subclasses in other packages genuinely need to override will force you to either make it public or redesign the inheritance hierarchy.
A final point: the Java compiler enforces these rules at compile time, so there is no runtime performance difference between protected and default. The choice is purely about code organization and API stability, not about execution speed.