Java 25 Flexible Constructor Bodies

August 7, 20265 min readUpdated 8/20/2026

Until Java 25, super() or this() had to be the very first statement in a constructor. Nothing could run before it. Flexible constructor bodies lift that restriction, which means you can finally validate an argument before handing it to the superclass.

The restriction

class Account {
    Account(double balance) {
        System.out.println("creating with " + balance);
    }
}

class SavingsAccount extends Account {
    private final double rate;

    SavingsAccount(double balance, double rate) {
        super(balance);                      // had to be first
        this.rate = rate;
    }
}

That rule existed for a good reason: the superclass must be fully initialised before the subclass touches anything, or a method call could observe a half-built object. But it was stricter than it needed to be, because plenty of useful work touches no fields at all.

The workaround it replaces

Validating an argument before super() meant a static helper method — a pattern everyone recognised and nobody liked:

class Account {
    Account(double balance) { }
}

class SavingsAccount extends Account {
    private final double rate;

    SavingsAccount(double balance, double rate) {
        super(validate(balance));            // a static call is legal in the argument
        this.rate = rate;
    }

    private static double validate(double balance) {
        if (balance < 0) {
            throw new IllegalArgumentException("balance cannot be negative: " + balance);
        }
        return balance;
    }
}

It works, and it forces the validation into a static method that returns its argument — which is an odd shape, and awkward when several parameters need checking or when the checks depend on each other.

The change

class Account {
    Account(double balance) { }
}

class SavingsAccount extends Account {
    private final double rate;

    SavingsAccount(double balance, double rate) {
        if (balance < 0) {
            throw new IllegalArgumentException("balance cannot be negative: " + balance);
        }
        if (rate < 0 || rate > 1) {
            throw new IllegalArgumentException("rate must be between 0 and 1: " + rate);
        }
        super(balance);                      // now legal after the checks
        this.rate = rate;
    }
}

The statements before super() are called the prologue. Everything after it is the epilogue, and behaves exactly as before.

What the prologue may not do

The safety guarantee is preserved, and it is preserved by a rule that is easy to state: before super() runs, you may not touch the instance under construction.

So the prologue cannot read or write a field, call an instance method, or use this in any way. It can:

  • read and validate the constructor's parameters
  • declare and compute local variables
  • throw
  • call static methods
class Base {
    Base(int value) { }
}

class Child extends Base {
    private int field = 10;

    Child(int input) {
        int doubled = input * 2;             // local variable: fine
        if (doubled > 100) {                 // validation: fine
            throw new IllegalArgumentException("too large");
        }
        // System.out.println(field);        // NOT allowed — reads the instance
        // helper();                         // NOT allowed — instance method
        super(doubled);
        System.out.println(field);           // fine here
    }

    void helper() { }
}

That restriction is why the feature is safe: nothing can observe the object before its superclass has finished with it, which was the whole reason for the original rule.

Doing real work in the prologue

class Config {
    Config(Map<String, String> settings) { }
}

class TypedConfig extends Config {
    private final int port;

    TypedConfig(String raw) {
        // Parse once, use twice — impossible before, without parsing twice
        // or hiding it in a static method
        Map<String, String> parsed = new HashMap<>();
        for (String pair : raw.split(",")) {
            String[] parts = pair.split("=", 2);
            if (parts.length == 2) {
                parsed.put(parts[0].strip(), parts[1].strip());
            }
        }
        int parsedPort = Integer.parseInt(parsed.getOrDefault("port", "8080"));

        super(parsed);
        this.port = parsedPort;
    }
}

This is the case the feature really improves. Deriving several values from one argument previously meant either computing it twice — once for super(), once for the field — or a static method returning a container.

It also works with this()

class Order {
    private final String id;
    private final int quantity;

    Order(String id, int quantity) {
        this.id = id;
        this.quantity = quantity;
    }

    Order(String id) {
        if (id == null || id.isBlank()) {
            throw new IllegalArgumentException("id required");
        }
        this(id.strip(), 1);                 // delegation, after validation
    }
}

The bug it helps prevent

There is a second, subtler benefit. The original rule forced a whole class of initialisation into the epilogue, and combined with inheritance that produces one of Java's classic traps — a superclass constructor calling an overridable method:

class Base {
    Base() {
        describe();                  // calls the OVERRIDE, before Child's fields are set
    }

    void describe() {
        System.out.println("base");
    }
}

class Child extends Base {
    private final String name = "child";

    @Override
    void describe() {
        System.out.println(name.length());   // NullPointerException: name is still null
    }
}

That throws, because Base's constructor runs first and name has not been assigned yet. It is a genuinely confusing failure — a final field with an initialiser, observed as null.

Flexible constructors do not remove this trap, and it is worth knowing they do not. What they do is remove one of the reasons people reached for the pattern in the first place: needing the subclass to contribute something before the superclass ran. Now the subclass can compute it in the prologue and pass it up as an argument, which is the correct shape.

The underlying advice stands regardless, and it is the one from OOP: do not call an overridable method from a constructor. Make it final, or private, or do not call it.

Does it matter?

It is a small, unglamorous change and it removes a real annoyance. The static-validator pattern was in a great deal of code and nobody wrote it because they wanted to.

The broader value is for records and value-based classes, where the argument for validating in the constructor is strongest — a record's compact constructor already allowed this, and inheritance-based classes were the outlier. Failing fast with a clear message, as the best practices post argues, is much easier when the language does not fight the shape of the check.

Next

Other Java 25 improvements is next.