Back to Blog
Java

Java Abstract Class Constructors Explained

java abstract class constructor: Learn how constructors work in Java abstract classes, including super() chaining, access modifiers, and shared state initialization.

JavaAbstract ClassConstructorsInheritancesuper()
Diagram showing an abstract class constructor being invoked through a subclass super() call during object creation.

Can you declare a constructor in an abstract class in Java? Yes, and the java abstract class constructor behaves differently from what many developers expect. It runs as part of every subclass instantiation, even though the abstract class itself can never be instantiated with new. This behavior is a frequent source of confusion because the constructor appears to belong to a class that cannot be created directly, yet it executes reliably every time a concrete subclass is constructed.

Why an Abstract Class Can Declare a Constructor

An abstract class is a normal class in most respects. It can have fields, methods, and constructors. The only restriction is that it cannot be instantiated directly with the new keyword. When a concrete subclass is created, the JVM invokes the abstract class's constructor as part of the construction chain, starting from the top of the inheritance hierarchy and working down.

public abstract class BaseRepository { private final DataSource dataSource; public BaseRepository(DataSource dataSource) { this.dataSource = dataSource; } public DataSource getDataSource() { return dataSource; } }

The constructor above initializes the dataSource field. Because the field is final, it must be assigned in the constructor. This is one of the main reasons abstract class constructors exist: they give you a place to initialize fields that every subclass depends on, and they guarantee that initialization happens before any subclass logic runs.

How Constructor Chaining Reaches the Abstract Class

When you create an instance of a concrete subclass, Java calls the subclass constructor, which must call a constructor of its direct superclass. If the subclass constructor does not explicitly call super(...), the compiler inserts a call to the no-argument super().

public class UserRepository extends BaseRepository { public UserRepository(DataSource dataSource) { super(dataSource); } }

If BaseRepository had only a parameterized constructor and no no-argument constructor, the subclass constructor above would fail to compile unless it explicitly called super(dataSource). This is the most common compile-time error related to abstract class constructors: the compiler reports that the constructor in the abstract class cannot be applied to the given types.

Construction order matters. The abstract class constructor executes before the subclass constructor body. Any state initialized in the abstract class is therefore available to the subclass constructor when it runs.

Choosing an Access Modifier for the Constructor

The access modifier on an abstract class constructor controls which subclasses can call it.

Access modifierWho can call itTypical use
protectedAny subclassMost common choice
publicAny codeRarely needed
package-privateSubclasses in the same packagePackage-scoped design
privateNo subclassPrevents extension entirely

A protected constructor is the most common choice because it allows any subclass to invoke it while preventing code outside the package from doing so. A public constructor is valid but rarely necessary, since the abstract class cannot be instantiated directly. A package-private constructor restricts construction to subclasses in the same package.

A private constructor compiles, but a subclass cannot call it. If all constructors are private, the class cannot be extended at all, which defeats the purpose of an abstract class. Private constructors in abstract classes only make sense when you want to prevent subclassing entirely, in which case the class should not be abstract.

Initializing Shared State Before Subclass Logic Runs

The constructor is the correct place to initialize fields that are shared across all subclasses. This includes configuration objects, connection pools, or any dependency that every subclass needs.

public abstract class PaymentProcessor { private final PaymentGateway gateway; private final Currency defaultCurrency; public PaymentProcessor(PaymentGateway gateway, Currency defaultCurrency) { this.gateway = gateway; this.defaultCurrency = defaultCurrency; } protected PaymentGateway getGateway() { return gateway; } protected Currency getDefaultCurrency() { return defaultCurrency; } }

Subclasses inherit these fields through the accessor methods and do not need to redeclare them. This keeps dependency wiring in one place instead of duplicating it in every subclass constructor. It also makes the required dependencies explicit: the constructor signature documents exactly what a subclass must supply.

Common Mistakes and Their Consequences

One frequent mistake is declaring a no-argument constructor in the abstract class while the subclass needs to pass arguments. If the abstract class has no no-argument constructor, every subclass must explicitly call super(...). Forgetting this produces a compile error, not a runtime failure, which is helpful but can be confusing when the error message points at the subclass constructor rather than the abstract class.

Another mistake is attempting to instantiate the abstract class directly:

BaseRepository repo = new BaseRepository(dataSource); // does not compile

This fails at compile time with an error stating that the class is abstract and cannot be instantiated. The fix is to create a concrete subclass.

A subtler issue is calling an overridable method from the abstract class constructor. Because the subclass constructor body has not run yet, subclass fields are still at their default values when the overridden method executes.

public abstract class ReportGenerator { public ReportGenerator() { String title = getTitle(); // overridden method } protected abstract String getTitle(); }

If the subclass's getTitle() depends on a field assigned in the subclass constructor, that field is still null when the abstract constructor calls the method. This initialization-order trap is well known, and it is a good reason to keep abstract constructor logic minimal and avoid calling overridable methods.

Maintainability and Design Tradeoffs

Abstract class constructors are useful when subclasses share initialization logic, but they also create a coupling point. Every change to the abstract constructor signature forces all subclasses to update their super(...) calls. If the set of required dependencies changes frequently, consider whether an abstract method that each subclass implements is a better fit.

A common alternative is the template method pattern, where the abstract class defines the skeleton of an algorithm and subclasses provide specific steps. In that pattern, the abstract class constructor typically stays minimal, and the subclass supplies the variable parts through abstract methods rather than constructor arguments.

There is also a compatibility consideration. Adding a new parameter to the abstract class constructor is a breaking change for all existing subclasses. If you need to evolve the constructor over time, you can keep the old constructor and add a new one that delegates to it, but this only works cleanly when the number of constructors remains small.

Runtime Behavior and Final Fields

Final fields in an abstract class must be assigned in the constructor. This constraint cannot be deferred to the subclass. If a field is final and not initialized at its declaration, the abstract class constructor must assign it. This is often the deciding factor for whether to use a constructor parameter versus an abstract getter method.

public abstract class Cache { private final int maxSize; public Cache(int maxSize) { this.maxSize = maxSize; } public int getMaxSize() { return maxSize; } }

The maxSize field is guaranteed to be set before any subclass code runs, which makes the invariant explicit. An abstract method that returns the size would defer the decision to the subclass and allow the field to remain uninitialized until the method is called. When you need a field that is immutable and shared across all subclasses, a constructor parameter is the safer choice than an abstract getter.

java abstract class constructor: Practical Usage and Code Ex | RYUSLOG DEV