🚀 Modern Java 17 → 25 · Advanced

Flexible constructor bodies in Java

Java 25: statements before super(...) that don't use this.

🧩 The mysteryFor almost 30 years, super(...) had to be the very first line of a constructor, even when you only wanted to check an argument first. Java 25 lets you validate before you build.

The old rule

Before Java 25, super(...) or this(...) had to be the first statement of a constructor. Want to validate an argument first? You needed contortions like super(checkAge(age)) with a static helper.

The prologue

Since Java 25 (JEP 513, final), statements may come before super(...): to validate or prepare arguments, as long as they don't use the object under construction.

// class Employee extends Person
Employee(String name, int age) {
    if (age < 18) {
        throw new IllegalArgumentException();
    }
    super(name);
}
🔮 Predict it

Your turn

What does this print?

class Base {
    Base(int v) { System.out.println(v); }
}
class Kid extends Base {
    Kid(int n) {
        System.out.println("K" + n);
        super(n * 2);
    }
}
void main() { new Kid(5); }
  1. K5 10
  2. 10 K5
  3. Compile error
Show the answer

The prologue runs first and prints K5. Then super(n * 2) runs the superclass constructor, which prints 10. Printing and doing math on arguments don't touch the new object, so they're allowed.

⚠️ The trap

What the prologue can't touch

Before super() the object isn't initialized yet, so the prologue may not read this, read its fields, or call instance methods. Static methods and work on the arguments are fine.

Account(String id) {
    Objects.requireNonNull(id);  // ok: static
    String clean = id.strip();   // ok: argument
    log(clean);  // error: instance method
    System.out.println(size);  // error: field
    super(clean);
}
🔮 Predict it

The classic surprise

The superclass constructor calls an overridden method. What does this print?

class Base {
    Base() { hello(); }
    void hello() {}
}
class Kid extends Base {
    String name = "Ann";
    void hello() { System.out.println(name); }
}
void main() { new Kid(); }
  1. Ann
  2. null
  3. Compile error
Show the answer

null. Base() runs before Kid's field initializers, so when it calls the overridden hello(), name hasn't been assigned yet. A decades-old trap.

Early assignment fixes it

One thing the prologue may do: assign the class's own fields. Assign tag before super() and it's already set when Base() calls the overridden show(). Assign it after super() and show() would print null.

class Kid extends Base {
    final String tag;
    Kid(String t) {
        tag = t;      // before super(): allowed
        super();      // Base() calls show()
    }
    void show() { System.out.println(tag); }
}
💼 In the real world

Fail fast, prepare once

Validate arguments before an expensive superclass constructor opens files or registers listeners, so a bad call fails immediately. Parse an input once in the prologue and pass the pieces to super(a, b), instead of parsing it twice in static helpers.

Key takeaways

  1. Validate or prepare arguments before calling super(...)
  2. The prologue can't read this, its fields, or instance methods
  3. Assigning your own fields before super() is allowed
  4. Final in Java 25 (JEP 513)
🤯 Did you know?

The JVM never strictly required super() first: its verifier already allowed code before the super call that doesn't use this. The restriction lived in the Java language.

Practice questions

What does this print?

class Base {
    Base() { System.out.println("Base"); }
}
class Kid extends Base {
    Kid() {
        System.out.println("prologue");
        super();
    }
}
void main() { new Kid(); }
  1. prologue Base
  2. Base prologue
  3. Compile error
  4. prologue
Check your answer

prologue Base. Statements before super() run first, then the superclass constructor. Printing doesn't touch the new object, so it's allowed in the prologue.

What does this print?

class Base {}
class Kid extends Base {
    int size = 3;
    Kid() {
        System.out.println(size);
        super();
    }
}
void main() { new Kid(); }
  1. Compile error
  2. 3
  3. 0
  4. Throws NullPointerException
Check your answer

Compile error. Reading the field size means reading this before the superclass constructor has run. The prologue may not use the object under construction.

Next: put it all together. Records + sealed types + patterns add up to a whole new way to model data.