super and Constructor Chaining

Control how a subclass object is built: super(...) and this(...) calls, the order things run in, and the checks Java 25 lets you do before super.

SyntaxSub(Type arg) { // Java 25: checks may go here, no use of this super(arg); // rest of initialisation }

A subclass object contains a superclass object, and the superclass part is built first. Every constructor therefore starts the chain upwards, either explicitly with super(...) or by calling another constructor of the same class with this(...). A constructor can contain one of the two, never both.

The hidden super()

If a constructor has neither call, the compiler inserts super() with no arguments. That works until the superclass only has constructors that take arguments; then the subclass fails to compile with a message that the superclass constructor cannot be applied to given types. The fix is to call the right one yourself.

class Order extends BaseEntity {
    Order(long id) {
        super(id);           // runs BaseEntity(long) first
    }
    Order() {
        this(0);             // delegates to Order(long)
    }
}

Order of construction

Construction runs top down: Object, then the superclass constructor, then the subclass's field initialisers, then the rest of the subclass constructor. That order is behind a real trap: if a superclass constructor calls a method the subclass overrides, the override runs while the subclass's fields are still null or 0. (A final field set to a compile-time constant, such as "/users", escapes this only because the compiler inlines the value.)

Statements before super in Java 25

Before Java 25, super(...) or this(...) had to be the first statement unless preview features were switched on. Java 25 finalised flexible constructor bodies (JEP 513, previewed in Java 22 to 24): statements may now come before the call. That code is the prologue. It may validate arguments, compute values and assign the class's own fields that have no initialiser, but it may not read fields or call instance methods, because the object is not ready yet.

Two practical wins: you can reject bad input before the superclass does any work, and you can set a subclass field before the superclass constructor runs, which defuses the trap above.

super.method()

Outside constructors, super.name() calls the superclass's version of an overridden method. Use it to extend behaviour (add a field to a description, add a header) instead of copying the superclass code into the override.

In service code, keep constructors short: assign dependencies, check arguments, nothing else. A constructor that opens connections or calls overridable methods is hard to test and easy to break from a subclass.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Reject an invalid amount or id in a subclass constructor before the superclass allocates anything.
  • Pass a fixed HTTP status from NotFoundException up to an ApiException constructor with super(404, message).
  • Offer a no-argument constructor for a configuration class that delegates to the full one with this(...) and defaults.

More examples

Validating arguments before super (Java 25)

The bad value is rejected before Money's constructor runs, so no half-built object and no wasted superclass work. Before Java 25 this check had to live in a static helper called inside the super(...) arguments.

Example · java
class Money {
    final long cents;
    Money(long cents) {
        IO.println("Money constructor ran");
        this.cents = cents;
    }
}

class Payment extends Money {
    Payment(long cents) {
        // Prologue: allowed in Java 25 because it does not use this.
        if (cents <= 0) {
            throw new IllegalArgumentException("payment must be positive, got " + cents);
        }
        super(cents);
    }
}

void main() {
    IO.println(new Payment(1500).cents);
    try {
        new Payment(-5);
    } catch (IllegalArgumentException e) {
        IO.println(e.getMessage());
    }
}

// Output:
// Money constructor ran
// 1500
// payment must be positive, got -5

The overridable call trap, and the Java 25 fix

UserHandler assigns its field after super() returns, so Handler's constructor sees null. Assigning the field in the prologue makes it ready in time, though the cleaner fix is still not to call overridable methods from a constructor.

Example · java
class Handler {
    Handler() {
        IO.println("registering " + route());
    }
    String route() { return "/"; }
}

class UserHandler extends Handler {
    private final String prefix;
    UserHandler() {
        super();
        this.prefix = "/users";   // too late: Handler already called route()
    }
    @Override String route() { return prefix; }
}

class OrderHandler extends Handler {
    private final String prefix;  // no initialiser, so the prologue may assign it
    OrderHandler() {
        this.prefix = "/orders";  // set before super() runs
        super();
    }
    @Override String route() { return prefix; }
}

void main() {
    new UserHandler();
    new OrderHandler();
}

// Output:
// registering null
// registering /orders

Extending a method with super.method()

super.describe() calls User's version even though the object is an Admin. If User later adds more fields to its description, Admin picks them up without changes.

Example · java
class User {
    private final String email;
    User(String email) { this.email = email; }

    String describe() { return "email=" + email; }
}

class Admin extends User {
    private final String scope;
    Admin(String email, String scope) {
        super(email);
        this.scope = scope;
    }

    @Override
    String describe() {
        // Reuse the superclass text, then add to it.
        return super.describe() + ", scope=" + scope;
    }
}

void main() {
    User u = new Admin("[email protected]", "billing");
    IO.println(u.describe());
}

// Output:
// [email protected], scope=billing

Discussion

  • Be the first to comment on this lesson.