How do I Validate Arguments Before Calling super() in Java 25?

Java 25 includes JEP 513: Flexible Constructor Bodies (finalized in Java 25 after being previewed in JEP 447, 482, and 492). This feature allows you to execute statements — including argument validation — before invoking super(...) or this(...), as long as those statements don’t access the instance being constructed.

The Old Problem (Pre-Java 22)

Before flexible constructor bodies, super(...) or this(...) had to be the first statement in a constructor. This forced awkward workarounds:

public class PositiveBigInteger extends BigInteger {
    public PositiveBigInteger(long value) {
        super(String.valueOf(value)); // must be first!
        if (value <= 0) {
            throw new IllegalArgumentException("Value must be positive");
        }
        // Superclass was already constructed with invalid data
    }
}

Workarounds required static helper methods:

public PositiveBigInteger(long value) {
    super(validate(value));
}
private static String validate(long value) {
    if (value <= 0) throw new IllegalArgumentException("Value must be positive");
    return String.valueOf(value);
}

The Java 25 Way — Prologue Code

You can now write a prologue — statements before super(...) — that validates, transforms, or prepares arguments:

public class PositiveBigInteger extends BigInteger {
    public PositiveBigInteger(long value) {
        // Prologue: fail fast BEFORE constructing the superclass
        if (value <= 0) {
            throw new IllegalArgumentException("Value must be positive, got: " + value);
        }
        super(String.valueOf(value));
        // Epilogue: normal constructor body
    }
}

Practical Examples

1. Validation with Transformation

public class EmailAddress {
    private final String normalized;

    public EmailAddress(String raw) {
        // Validate & normalize BEFORE any field assignment or super() call
        Objects.requireNonNull(raw, "raw email must not be null");
        String trimmed = raw.trim().toLowerCase();
        if (!trimmed.contains("@")) {
            throw new IllegalArgumentException("Invalid email: " + raw);
        }
        this.normalized = trimmed; // field assignment is allowed in prologue
    }
}

2. Sharing Expensive Computation Between this() Chains

public class Matrix {
    public Matrix(double[][] data) {
        // Validate once, use twice
        if (data == null || data.length == 0) {
            throw new IllegalArgumentException("Matrix cannot be empty");
        }
        int rows = data.length;
        int cols = data[0].length;
        this(rows, cols, flatten(data));
    }

    public Matrix(int rows, int cols, double[] flat) { /* ... */ }
}

3. Conditional Superclass Arguments

public class LoggingList<E> extends ArrayList<E> {
    public LoggingList(Collection<? extends E> source) {
        // Compute capacity hint before calling super
        int capacity = (source == null) ? 10 : Math.max(16, source.size() * 2);
        super(capacity);
        if (source != null) addAll(source);
    }
}

Rules You Must Follow

The prologue code runs before the instance is fully initialized, so the compiler enforces strict rules:

Allowed in prologue Not allowed in prologue
Reading/writing local variables Reading this.field
Assigning to own instance fields (this.x = ...) Calling instance methods on this
Throwing exceptions Using this as an expression
Calling static methods Referring to super.field
Using constructor parameters Reading fields declared in the superclass
public class Example extends Parent {
    private final int value;

    public Example(int input) {
        if (input < 0) throw new IllegalArgumentException(); // OK
        this.value = input * 2;                              // OK (own field write)
        // System.out.println(this.value);                   // Can't READ own field
        // someInstanceMethod();                             // Can't call instance method
        super(computeParentArg(input));                      // static call is fine
    }

    private static int computeParentArg(int x) { return x + 1; }
}

Key Benefits

  1. Fail fast — reject invalid arguments before allocating superclass state
  2. No more static helper methods just to satisfy the “super must be first” rule
  3. Cleaner code — validation lives next to the constructor that needs it
  4. Better performance — avoid partially constructing objects that will be discarded
  5. DRY — share computation between multiple this(...) delegations

How do I use the this, super, and @ labels for disambiguation in Kotlin class hierarchies?

In Kotlin, this, super, and @ labels are used to disambiguate which receiver or superclass member you mean, especially in nested scopes, inheritance, and inner classes.

1. this: refer to the current receiver

Inside a class, this refers to the current instance of that class.

class User(val name: String) {
    fun printName() {
        println(this.name)
    }
}

Usually this is optional:

println(name)

is the same as:

println(this.name)

2. this@Label: choose a specific outer receiver

When you have nested classes, lambdas, or extension functions, there may be multiple possible this receivers. Kotlin lets you qualify this with a label.

Class receiver

class Outer {
    val name = "Outer"

    inner class Inner {
        val name = "Inner"

        fun printNames() {
            println(this.name)        // Inner
            println([email protected])  // Inner
            println([email protected])  // Outer
        }
    }
}

this@Outer explicitly means “the this of Outer”.


3. Labels in lambdas

You can label lambdas and then use this@label to access that lambda’s receiver.

class Html {
    fun body() {
        println("body")
    }
}

fun html(block: Html.() -> Unit) {
    Html().block()
}

fun main() {
    html outer@ {
        this.body()
        [email protected]()
    }
}

Here:

this@outer

refers to the receiver of the lambda labeled outer.


4. super: call superclass implementation

Use super to access a member from the immediate superclass.

open class Parent {
    open fun greet() {
        println("Hello from Parent")
    }
}

class Child : Parent() {
    override fun greet() {
        super.greet()
        println("Hello from Child")
    }
}

Output:

Hello from Parent
Hello from Child

5. super<Type>: disambiguate multiple inherited implementations

If a class inherits the same member from multiple supertypes, you must specify which one to call.

interface A {
    fun greet() {
        println("Hello from A")
    }
}

interface B {
    fun greet() {
        println("Hello from B")
    }
}

class C : A, B {
    override fun greet() {
        super<A>.greet()
        super<B>.greet()
        println("Hello from C")
    }
}

Here:

super<A>.greet()
super<B>.greet()

select the specific supertype implementation.


6. super@Label: access an outer class’s superclass

In inner classes, super normally refers to the superclass of the inner class. If you need the superclass of an outer class, use a qualified super.

open class Base {
    open fun message() {
        println("Base")
    }
}

open class OuterBase : Base() {
    override fun message() {
        println("OuterBase")
    }
}

class Outer : OuterBase() {
    override fun message() {
        println("Outer")
    }

    inner class Inner {
        fun callOuterSuper() {
            [email protected]()
        }
    }
}

Here:

[email protected]()

means “call the superclass implementation of Outer”.

So this calls:

OuterBase.message()

not Outer.message().


7. Combining super<Type>@Label

If the outer class implements multiple supertypes, you can combine both forms.

interface A {
    fun print() {
        println("A")
    }
}

interface B {
    fun print() {
        println("B")
    }
}

class Outer : A, B {
    override fun print() {
        println("Outer")
    }

    inner class Inner {
        fun callOuterSupers() {
            super<A>@Outer.print()
            super<B>@Outer.print()
        }
    }
}

Here:

super<A>@Outer.print()
super<B>@Outer.print()

means:

  • call A’s implementation as inherited by Outer
  • call B’s implementation as inherited by Outer

Summary

Syntax Meaning
this Current receiver
this@Outer this of a specific labeled or outer receiver
super Immediate superclass implementation
super<Type> Specific superclass or interface implementation
super@Outer Superclass implementation of an outer class
super<Type>@Outer Specific supertype implementation of an outer class

In short:

this@Something

chooses which object/receiver you mean.

super<Something>

chooses which superclass/interface implementation you mean.

super<Something>@Outer

chooses which supertype implementation of which outer receiver you mean.

How do I invoke superclass constructor?

This example shows you how to use the super keyword to call a superclass constructor. The Female class constructor calls its superclass constructor and initializes its own initialization parameters. The call to the superclass constructor must be done in the first line of the constructor in the subclass.

package org.kodejava.example.fundamental;

public class Human {
    private String gender;
    private int age;

    public Human(String gender) {
        this.gender = gender;
    }
}

To call a superclass constructor we call super(). In the case below we call the superclass constructor with one string variable as a parameter.

package org.kodejava.example.fundamental;

public class Female extends Human {
    private String hairStyle;

    public Female(String hairStyle, String gender) {
        super(gender);
        this.hairStyle = hairStyle;
    }
}

How do I use the super keyword?

When a class extends from other class, the class or usually called as subclass inherits all the accessible members and methods of the superclass. If the subclass overrides a method provided by its superclass, a way to access the method defined in the superclass is through the super keyword.

package org.kodejava.basic;

public class Bike {
    public void moveForward() {
        System.out.println("Bike: Move Forward.");
    }
}

In the ThreeWheelsBike‘s moveForward() method we call the overridden method using the super.moveForward() which will print the message from the Bike class.

package org.kodejava.basic;

public class ThreeWheelsBike extends Bike {
    public static void main(String[] args) {
        Bike bike = new ThreeWheelsBike();
        bike.moveForward();
    }

    @Override
    public void moveForward() {
        super.moveForward();
        System.out.println("Three Wheels Bike: Move Forward.");
    }
}