Back to Blog
Java

Java Protected Constructor: Access Rules and Use Cases

java protected constructor: Learn how Java protected constructors control instantiation: access rules, subclass usage, factory patterns, and when to choose protected o...

javaconstructorsaccess modifiersinheritanceobject-oriented design
Illustration of a Java protected constructor restricting access to subclasses and same-package classes while blocking unrelated code.

The java protected constructor occupies a narrow access window: it allows instantiation from within the same package and from subclasses, while blocking direct construction from unrelated code. That deliberate restriction makes it a useful tool for class hierarchies where the constructor should not be a general-purpose entry point.

The Access Rule Behind a Protected Constructor

Java's protected modifier grants access in two directions: any code in the same package, and any subclass regardless of package. For constructors this means new SomeClass(...) is legal from the same package, and super(...) is legal from a subclass. From unrelated code in a different package, neither form compiles.

There is a subtlety for cross-package subclasses. A subclass in another package can call the protected constructor through super(...), but it cannot write new BaseClass(...) even inside its own code. The protected member is only reachable through a reference of the subclass type. This qualified-reference rule applies to all protected members, and it matters for constructors because new always uses the declared type.

package com.example.shapes; public class Shape { protected Shape(String name) { this.name = name; } private final String name; public String name() { return name; } }

A subclass in the same package can use the constructor freely:

package com.example.shapes; public final class Circle extends Shape { public Circle(double radius) { super("circle"); this.radius = radius; } private final double radius; public double area() { return Math.PI * radius * radius; } }

How Subclasses in Another Package Call the Constructor

When the subclass lives in a different package, the super(...) call still compiles, but a direct new Shape(...) does not.

package com.example.app; import com.example.shapes.Shape; public final class CustomShape extends Shape { public CustomShape() { super("custom"); } }

Inside CustomShape, the line Shape s = new Shape("custom"); would fail to compile because the constructor is protected and the reference type is Shape, not CustomShape. The same restriction applies to any protected member: a subclass can access it only through a reference of that subclass type or a subtype.

This distinction matters when you design a hierarchy that spans packages. If the base constructor is protected, subclasses can initialize the base state, but no external code can construct the base directly. That is often exactly what a base class needs without declaring the class abstract.

Factory Methods That Use a Protected Constructor

A protected constructor pairs naturally with static factory methods. The factory lives in the same class or the same package, so it can invoke the constructor, while callers outside the package receive instances only through the factory.

package com.example.config; public final class AppConfig { private AppConfig(String path) { this.path = path; } private final String path; public static AppConfig fromFile(String path) { return new AppConfig(path); } public static AppConfig defaults() { return new AppConfig("/etc/app/default.properties"); } }

Here the constructor is private, which is the stricter choice when no subclassing is intended. The protected variant becomes useful when the class is meant to be subclassed and the factory should remain the only public entry point. A subclass can call the protected constructor through super(...), while unrelated code must go through the factory.

package com.example.config; public class DatabaseConfig extends AppConfig { public DatabaseConfig(String path) { super(path); } }

The factory methods in AppConfig return AppConfig, so callers who need DatabaseConfig still construct it directly. The protected constructor keeps the base class constructible for subclasses without exposing it as a public API.

Shared-Instance Patterns and Protected Constructors

Singleton and shared-instance patterns sometimes use a protected constructor when the class may be subclassed in tests or in specialized deployments. The static holder still controls the single instance, and a subclass can construct its own instance when needed.

package com.example.cache; public class Cache { protected Cache() { } private static final class Holder { private static final Cache INSTANCE = new Cache(); } public static Cache instance() { return Holder.INSTANCE; } }

A test subclass can then create its own instance:

package com.example.cache.test; import com.example.cache.Cache; public final class TestCache extends Cache { public TestCache() { super(); } }

The protected constructor is what makes this possible. A private constructor would force the test to use reflection, and a public constructor would allow accidental direct instantiation in production code.

Choosing Between Private, Protected, and Package-Private Constructors

The decision comes down to who is allowed to construct the object.

Constructor visibilitySame packageSubclass in another packageUnrelated code
privatenonono
package-privateyesnono
protectedyesyes, via super(...)no
publicyesyesyes

Use private when the class is final or when no subclass should ever construct it. Use package-private when only classes in the same package should construct it and subclassing is not an extension point. Use protected when subclassing is a supported extension point but direct instantiation by unrelated code should be blocked. Use public only when the constructor is a legitimate part of the public API.

The protected choice is not a hard security boundary. Any class in the same package can call the constructor, and reflection with setAccessible(true) can bypass visibility entirely. The modifier expresses intent about the API surface, not a security guarantee.

Common Mistakes with Protected Constructors

The most common mistake is assuming that protected means "subclass only." It also grants access to every class in the same package. If the goal is to prevent all direct instantiation except from subclasses, and the class is in a package shared with many other classes, protected may be too permissive.

Another mistake is writing a cross-package subclass that tries to call the constructor with new:

package com.example.app; import com.example.shapes.Shape; public final class BrokenShape extends Shape { public BrokenShape() { // Shape s = new Shape("broken"); // does not compile super("broken"); } }

The commented line fails because the reference type is Shape, not BrokenShape. The super(...) call is the only legal path.

A third issue appears when a base class has a protected constructor with parameters and subclasses forget the explicit super(...) call. If the base class declares no no-arg constructor, the subclass must call super(...) with the required arguments, and the compiler enforces it.

Maintainability and Compatibility Considerations

A protected constructor is a commitment to subclassing. External code can extend the class, which means the constructor signature becomes part of the supported API. Changing the parameter list, removing the constructor, or making it private will break subclasses that you do not control.

If the class is meant to be extended only within your own codebase, protected is a reasonable default. If the class is published as a library and subclassing is not a designed extension point, prefer private or package-private to keep the construction surface small. The narrower the constructor visibility, the more freedom you keep to change the class internals later.

The same reasoning applies to the class itself. A protected constructor on a non-final class invites subclassing. If you do not intend that, mark the class final and use a private constructor instead. The combination of final and a protected constructor is contradictory: a final class cannot be subclassed, so the protected access is effectively package-private.

java protected constructor: Practical Usage and Code Example | RYUSLOG DEV