skip to content
everything.modern.flexibleConstructors

flexible constructor bodies

statements before super(), finally.

chapter
chapter 08.09
reading time
7 min read
java 25+

since java 25 (JEP 513), a constructor can run statements before super(...) or this(...). that part is the prologue: it can validate arguments, compute values for the superclass constructor, and assign its own fields, all before any superclass code runs.

what it still can't do is touch the object: no reading fields, no calling instance methods, no passing this anywhere. the object isn't built yet, and the compiler makes sure nobody finds out.

the picture

you know the rule "super(...) must be the first statement in a constructor"? it was there from java 1.0, and it made simple things awkward. want to check an argument before handing it to the superclass? you couldn't, so you wrote a static helper and called it inside the super(...) arguments, or you let the superclass do work with a bad value and threw afterwards.

think of building a house. the old rule said the foundation crew (the superclass) has to start before you've even looked at the plans: you can't check the plot, can't measure anything, just pour concrete and hope. the new rule lets you survey the land and check the permits first, as long as you don't try to move furniture into a house that doesn't exist yet.

don'tbefore java 25: logic squeezed into a static helper
java
class Employee extends Person {
    Employee(String rawName) {
        super(clean(rawName));
    }
 
    private static String clean(String raw) {
        if (raw == null || raw.isBlank())
            throw new IllegalArgumentException("no name");
        return raw.strip().toUpperCase();
    }
}
dojava 25: just write it in order
java
class Employee extends Person {
    Employee(String rawName) {
        if (rawName == null || rawName.isBlank())
            throw new IllegalArgumentException("no name");
        String name = rawName.strip().toUpperCase();
        super(name);
    }
}

same behaviour, but the right side reads top to bottom: check, prepare, construct. compile the right side with --release 24 and javac 25 refuses:

text
error: flexible constructors is not supported in -source 24

prologue and epilogue

with statements allowed before super(...), a constructor has two halves. the prologue runs before the superclass is constructed, the epilogue after. across a hierarchy that gives a neat order: prologues run from the subclass up, epilogues from the top down:

hover a stage.

three classes, C extends B extends A, each printing in its prologue and its epilogue. this is the real output of new C() on jdk 25.

the real win: fields set before the superclass looks

here's the bug that the old rule made unavoidable. Person's constructor calls show(), an overridable method, and Employee overrides it to print a field of its own. switch between the two orders:

Main.java next to run
  1. 1class Person {
  2. 2 final String name;
  3. 3 final int age;
  4. 4
  5. 5 Person(String name, int age) {
  6. 6 this.name = name;
  7. 7 this.age = age;
  8. 8 show();
  9. 9 }
  10. 10
  11. 11 void show() { IO.println(name + ", " + age); }
  12. 12}
  13. 13
  14. 14class Employee extends Person {
  15. 15 final String officeId;
  16. 16
  17. 17 Employee(String name, int age, String officeId) {
  18. 18 if (age < 18) throw new IllegalArgumentException();
  19. 19 this.officeId = officeId;
  20. 20 super(name, age);
  21. 21 }
  22. 22
  23. 23 @Override
  24. 24 void show() { IO.println(name + ", office " + officeId); }
  25. 25}
  26. 26
  27. 27void main() {
  28. 28 new Employee("ada", 36, "CAM-FORA");
  29. 29}
stdout
·
stack
main
no locals yet
heap
nothing allocated yet.

main is about to build an Employee.

step 1/9
step through both tabs. the old order prints null for a final field that is never null once construction finishes. assigning the field in the prologue fixes it. the output matches jdk 25.
Main.java
class Person {
    final String name;
    final int age;
 
    Person(String name, int age) {
        this.name = name;
        this.age = age;
        show();                              // an overridable call: careful
    }
 
    void show() { IO.println(name + ", " + age); }
}
 
class Employee extends Person {
    final String officeId;
 
    Employee(String name, int age, String officeId) {
        if (age < 18) throw new IllegalArgumentException("too young: " + age);
        this.officeId = officeId;            // before super(...): new in java 25
        super(name, age);
    }
 
    @Override
    void show() { IO.println(name + ", office " + officeId); }
}
 
void main() {
    new Employee("ada", 36, "CAM-FORA");
    new Employee("bob", 16, "CAM-FORA");
}
output
ada, office CAM-FORA
Exception in thread "main" java.lang.IllegalArgumentException: too young: 16

the second new throws before Person ever runs. fail fast, no half-built object, no wasted superclass work. (calling overridable methods from a constructor is still a bad idea, effective java item 19 says so for good reasons. but now the subclass can at least defend itself.)

what the prologue still can't do

the object is allocated (memory, zeroed fields) but not constructed, so anything that could observe it is off limits. every one of these is rejected by javac 25:

in the prologuejavac 25 says
read a field: int y = a;cannot reference a before supertype constructor has been called
call an instance method: hashCode();cannot reference hashCode() before supertype constructor has been called
use this: System.out.println(this);cannot reference this before supertype constructor has been called
call a superclass method: super.m();cannot reference super before supertype constructor has been called
assign a field that has an initializercannot assign initialized field 's' before supertype constructor has been called
return;'return' not allowed before explicit constructor invocation
if (b) super(1); else super(2);explicit constructor invocation not allowed here

what is allowed: locals, arguments, static methods and fields, throwing, and assigning your own fields that don't have an initializer. a lambda in the prologue counts as touching this if it mentions a field, so that's out too.

predict the output
java
record Range(int lo, int hi) {
    Range(String text) {                     // "3-7"
        String[] parts = text.strip().split("-");
        this(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]));
    }
}
 
IO.println(new Range(" 3-7 "));
one level deeper:why the old rule existed, and why it could go+

the JVM never required super(...) to come first. its rules are looser: on every code path the superclass constructor is called exactly once (and not inside a try), and until then the half-built object is off limits except for assigning fields. javac itself has always relied on that, for instance to store the hidden outer-instance field of an inner class before calling super. the "first statement" rule was the language being stricter than the JVM, because "first" was easy to check and guaranteed "not used". JEP 513 swaps it for the real requirement, checked in what the spec now calls an early construction context, and needed no JVM change at all. it previewed three times: as "statements before super(...)" (JEP 447, java 22), as "flexible constructor bodies" with early field assignment (JEP 482, 23, and JEP 492, 24), and went final unchanged in java 25.

keylines
  • java 25: statements may come before super(...) or this(...).
  • the prologue validates, prepares arguments, and may assign its own uninitialized fields.
  • it can't touch the object: no field reads, no instance methods, no this, no super.x.
  • prologues run subclass-first, epilogues superclass-first.
  • set fields in the prologue and an overridden method called by the superclass sees real values.