Core Java

Flexible Constructor Bodies: Validating Before super()

Here’s a small confession. For years I wrote constructors that did their validation in the wrong place, and I knew it. The check belonged at the very top, before any parent class got to do its work. But Java would not let me put it there.

That rule is finally gone. Since JDK 25, a constructor can run its own statements before calling super(...) or this(...). Let’s look at why the old rule hurt, what the new model looks like, and where the limits are.

The rule you have been obeying forever

Until recently, the first statement of every constructor had to be a call to another constructor, either super(...) or this(...). If you wrote neither, the compiler quietly inserted super() for you. The JEP behind the change says this top-down rule has been enforced since the language was created.

The intent was good. A superclass constructor runs first, so by the time a subclass constructor starts, the inherited fields are valid. Simple, predictable, safe.

But the rule was blunter than the problem it solved. It banned safe code along with unsafe code.

Why the old rule hurt

Validation had to happen too late, or in a contortion

Say you have an Employee that extends Person, and employees must be between 18 and 67. You would like to reject a bad age immediately. Before JDK 25, you had two choices.

The first was a static helper called inline, inside the super(...) argument list:

class Employee extends Person {
    private static int checkAge(int age) {
        if (age < 18 || age > 67)
            throw new IllegalArgumentException("employee age out of range: " + age);
        return age;
    }

    Employee(int age) {
        super(checkAge(age));
    }
}

It works, but it reads like a riddle. The real validation lives in a different method from the constructor it protects.

The second choice was to call super(age) first and validate afterward. That is easier to read, but it does pointless work. I ran a version of it on JDK 25 with an age of 12:

Person constructor ran
Age: 12
caught: employee age out of range: 12

The parent constructor ran to completion and even printed a line, all to build an object we were about to throw away. In a real system that “work” might be a database lookup or a registration call. Not great.

The half-built subclass

The second problem is nastier, and it has nothing to do with validation. Think about what a superclass constructor can reach while it runs.

Suppose Person’s constructor calls an overridable method, show(), and Employee overrides it to print an officeID field. Here is the old-style subclass:

class Old extends Person {
    String officeID;

    @Override void show() {
        System.out.println("Age: " + this.age);
        System.out.println("Office: " + this.officeID);
    }

    Old(int age, String officeID) {
        super(age);               // Person's constructor calls show() in here
        this.officeID = officeID; // too late
    }
}

Running new Old(42, "ATH-12") on JDK 25 printed this:

Person constructor ran
Age: 42
Office: null

Office: null. The subclass method ran before the subclass had assigned its own field. The JEP notes that even final fields can be observed changing value this way. Effective Java has long advised against calling overridable methods from constructors, and the JEP cites that item. Still, it happens in real codebases, and the language gave you no way to defend your own class against it.

So how do you fix it? You assign the field before the parent gets a chance to look.

The fix: statements before super()

With JDK 25, the constructor can do exactly that:

class New extends Person {
    String officeID;

    @Override void show() {
        System.out.println("Age: " + this.age);
        System.out.println("Office: " + this.officeID);
    }

    New(int age, String officeID) {
        if (age < 18 || age > 67)
            throw new IllegalArgumentException("employee age out of range: " + age);
        this.officeID = officeID;  // set our own field first
        super(age);
    }
}v

Two things changed. The age check now fails fast, before Person does anything. And officeID is assigned before the parent constructor can call show(). The output from new New(42, "ATH-12") was:

Person constructor ran
Age: 42
Office: ATH-12

Compare the two runs side by side.

That symmetry is the point. Prologues establish validity from the bottom of the hierarchy upward, and epilogues can then rely on it.

What the prologue may and may not do

There is a catch, and it is the heart of the safety story. Code in the prologue (and in the argument list of the constructor call) lives in what the JEP calls an early construction context. In that zone you must not use the object under construction.

Concretely, you cannot touch this, read the object’s fields, or call its methods. You also cannot use super.something to reach the parent. The one exception is simple assignment to fields declared in the same class, as long as those fields have no initializer of their own.

Here is a class I tried that breaks the rule:

class Bad extends Person {
    int x;
    Bad(int age) {
        System.out.println(this.x);  // reads the half-built object
        super(age);
    }
}

The JDK 25 compiler rejected it with cannot reference this before supertype constructor has been called. That is the guardrail doing its job.

Nested classes get one extra allowance. An inner class constructor can use its enclosing instance in the prologue, because that outer object already exists. The reverse is not allowed: an outer constructor cannot create an inner instance early, since new Inner() implicitly means this.new Inner().

Patterns that now read naturally

Parse once, pass twice

Sometimes a constructor needs to compute something and feed it to the parent in more than one argument. Before, you needed a helper for each use, or a clumsy intermediate. Now you can just compute it:

class ParsedMoney extends Money {
    ParsedMoney(String text) {
        String[] parts = text.trim().split("\\s+");
        if (parts.length != 2)
            throw new IllegalArgumentException("expected '<amount> <currency>': " + text);
        long cents = Math.round(Double.parseDouble(parts[0]) * 100);
        super(cents, parts[1].toUpperCase());
    }
}

On JDK 25, new ParsedMoney(" 12.50 eur ") produced 1250 EUR, and a malformed string failed fast with a clear message.

Records and enums

Non-canonical record constructors must call this(...), and until now that call had to come first. Records benefit from the new rules too:

record Range(int lo, int hi) {
    Range(int hi) {
        if (hi < 0) throw new IllegalArgumentException("hi < 0");
        this(0, hi);
    }
}

new Range(5) gave Range[lo=0, hi=5], and new Range(-3) threw before delegating. Enum constructors gain the same freedom for alternate constructor calls. The existing record restrictions still apply: canonical record constructors cannot contain an explicit constructor call at all.

A few rules that did not change

A constructor call still cannot appear inside a try block, so you cannot wrap super(...) in exception handling. A return statement is allowed in the epilogue, but only without a value, and never in the prologue. Throwing exceptions is fine in either phase, and in the prologue it is the typical fail-fast move.

Also worth knowing: the change needed no JVM specification update. According to the JEP, the JVM already accepted this shape of bytecode for years, mainly to support compiler-generated code for features like inner classes. The Java language rules were simply stricter than the JVM’s. The proposal also reports that the existing JDK classes compile to identical bytecode before and after.

How we got here

This was not a rushed feature. It first appeared as a preview in JDK 22 under JEP 447, under a different title. JEP 482 revised it for JDK 23, and JEP 492 previewed it again, unchanged, in JDK 24. Then JEP 513 finalized it in JDK 25, which is a long-term support release, according to Azul’s guide to JDK 25.

So if you are on JDK 25 or later, no preview flag is needed. On 22 through 24 you would have needed to enable previews.

Before you rush to use it

The JEP itself warns about one real cost: the “constructor invocation comes first” assumption is baked into tools. Linters, style checkers, syntax highlighters and IDE inspections may need updates, so expect a period of friction. I did not test any third-party tooling, so check yours before rolling this out across a large codebase.

A quick checklist helps:

  • Move validation into the prologue when it lets you fail before the parent does work.
  • Initialize your own fields in the prologue when a parent constructor might call overridable methods.
  • Keep prologue code free of this. If you need the instance, wait for the epilogue.
  • Better yet, stop calling overridable methods from constructors. The new feature protects you, but it does not make that habit a good one.

Conclusion

Here is what we covered. The old rule, that super(...) or this(...) must come first, protected object integrity but blocked safe code, which pushed validation into awkward helpers or into wasteful late checks. It also left subclasses exposed: in my run, a parent constructor happily printed Office: null from a field the subclass had not yet set.

JDK 25 replaces the syntactic rule with a better model. Constructors now have a prologue that runs subclass-first and an epilogue that runs superclass-first, and an early construction context keeps the prologue away from the half-built object. You get fail-fast validation, cleaner argument preparation, and the ability to initialize your own state before anyone else looks at it.

The practical move is small. The next time you write a static helper just to sneak a check into super(...), delete it and write the check where it belongs.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button