How do I Initialize Fields Before Calling super() in Java 25?

Java 25 includes JEP 513: Flexible Constructor Bodies. This feature changes a long-standing rule: before Java 22, the call to super(...) or this(...) had to be the very first statement in a constructor. Starting with Java 25, you can execute statements before the explicit constructor invocation — including initializing your own instance fields.

This is especially useful when a field’s value must be ready before the superclass constructor runs (for example, if the superclass calls an overridden method that reads that field).


The Old Restriction (Pre-Java 22)

public class Child extends Parent {
    private final int value;

    public Child(int input) {
        // Compile error before Java 22:
        // "call to super must be first statement in constructor"
        this.value = input * 2;
        super(computeSomething(input));
    }
}

The Java 25 Way: Prologue Before super(...)

With flexible constructor bodies, the constructor is split into two regions:

  1. Prologue — statements that run before super(...) or this(...).
  2. Epilogue — the usual constructor body that runs after.

Example 1: Initializing a Field Before super()

public class Parent {
    public Parent() {
        // Calls a method that subclasses may override
        describe();
    }

    protected void describe() {
        System.out.println("Parent describing");
    }
}

public class Child extends Parent {
    private final String label;

    public Child(String label) {
        // Java 25: initialize field BEFORE super()
        this.label = label;
        super();
    }

    @Override
    protected void describe() {
        // Safe: 'label' is already assigned, no NPE
        System.out.println("Child label = " + label);
    }
}

Before Java 25, label would still be null when Parent‘s constructor called the overridden describe(). Now you can assign it first.


The Prologue Rules You Must Follow

Inside the prologue (code before super(...) / this(...)), the compiler enforces strict rules to keep things safe:

Allowed Not Allowed
Assign to fields declared in this class Read fields of this
Validate / transform constructor arguments Call instance methods on this
Compute values, throw exceptions, use if, switch, loops, try Use this or super as a value
Call static methods Access inherited (superclass) fields

Example 2: Combining Validation and Field Init

public class Rectangle {
    protected final int area;

    public Rectangle(int area) {
        this.area = area;
    }
}

public class Square extends Rectangle {
    private final int side;

    public Square(int side) {
        // Prologue: validate, then initialize own field, then call super
        if (side <= 0) {
            throw new IllegalArgumentException("side must be positive");
        }
        this.side = side;          // assigning own field is allowed
        super(side * side);        // explicit constructor invocation
        // Epilogue starts here — 'this' is fully available
        System.out.println("Created Square with side=" + side + ", area=" + area);
    }
}

Example 3: Why This Matters — Fixing the “Leaked this” Problem

public abstract class Shape {
    public Shape() {
        render(); // calls subclass override during construction
    }
    protected abstract void render();
}

public class Circle extends Shape {
    private final double radius;

    public Circle(double radius) {
        this.radius = radius; // set before super() runs render()
        super();
    }

    @Override
    protected void render() {
        // Now 'radius' has its intended value, not 0.0
        System.out.println("Rendering circle with radius " + radius);
    }
}

Compiling and Running on Java 25

Since JEP 513 is a final feature in Java 25, no --enable-preview flag is required:

javac --release 25 Child.java Parent.java
java Child

Key Takeaways

  • You can now place statements — including assignments to your own fields — before super(...) or this(...).
  • The prologue cannot read this or call instance methods; it can only write to fields of the current class.
  • Exactly one explicit constructor invocation (super(...) or this(...)) is still required (implicitly or explicitly).
  • This feature is final in Java 25 — no preview flags needed.

This cleanly resolves cases where a subclass field needed to exist before the parent constructor ran, without resorting to static helpers or awkward factory methods.

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 choose the right scope function for different use cases in Kotlin?

Kotlin scope functions all do the same broad thing: they execute a block of code in the context of an object. The main differences are:

  1. How you refer to the object: this or it
  2. What the function returns: the object itself or the block result

Quick decision table

Function Object reference Returns Best for
let it Lambda result Transforming a value, null-safe execution
run this Lambda result Computing a result from an object
with this Lambda result Grouping operations on an existing non-null object
apply this The original object Configuring or initializing an object
also it The original object Side effects like logging, validation, debugging

Use let when you want to transform a value

let is useful when you want to take an object and produce another value.

val name = "kotlin"

val length = name.let {
    it.uppercase().length
}

println(length) // 6

It is also very common with nullable values:

val email: String? = "[email protected]"

email?.let {
    println("Sending email to $it")
}

Use let when you are thinking:

“If this value exists, do something with it or turn it into something else.”


Use apply when you want to configure an object

apply returns the original object, so it is ideal for initialization.

data class User(
    var name: String = "",
    var age: Int = 0,
    var active: Boolean = false
)

val user = User().apply {
    name = "Alice"
    age = 30
    active = true
}

Inside apply, the object is available as this, so you can access its properties directly.

Use apply when you are thinking:

“Create this object and set it up.”


Use also when you want side effects without changing the chain result

also returns the original object, like apply, but the object is referenced as it.

This makes it good for logging, debugging, validation, or extra actions.

val numbers = mutableListOf(1, 2, 3)
    .also {
        println("Original list: $it")
    }
    .apply {
        add(4)
    }
    .also {
        println("Updated list: $it")
    }

println(numbers) // [1, 2, 3, 4]

Use also when you are thinking:

“Do this extra thing, but keep passing the same object along.”


Use run when you want to compute a result using an object

run uses this and returns the lambda result.

val message = StringBuilder().run {
    append("Hello, ")
    append("Kotlin")
    toString()
}

println(message) // Hello, Kotlin

This is useful when you want to perform multiple operations and return a final computed value.

Use run when you are thinking:

“Use this object to calculate a result.”


Use with when you already have an object and want to group operations

with is not called as an extension in the same style as the others. You pass the object as an argument.

val builder = StringBuilder()

val result = with(builder) {
    append("Hello, ")
    append("Kotlin")
    toString()
}

println(result) // Hello, Kotlin

Use with when you are thinking:

“With this existing object, do several things.”

with is usually best for non-null objects. For nullable values, prefer ?.let or ?.run.


The simplest way to choose

Ask two questions:

1. Do you want to return the original object?

If yes:

  • Use apply for configuration
  • Use also for side effects
val user = User().apply {
    name = "Alice"
}
val user = getUser().also {
    println("Loaded user: $it")
}

2. Do you want to return a new result?

If yes:

  • Use let when you prefer it
  • Use run when you prefer this
  • Use with when the object already exists and is non-null
val length = name.let {
    it.length
}
val text = builder.run {
    append("Done")
    toString()
}

Rule of thumb

let   = transform or null-check
run   = compute a result using this object
with  = operate on an existing object
apply = configure an object
also  = perform side effects

Common examples

Null-safe call

val username: String? = "sam"

username?.let {
    println("Username length: ${it.length}")
}

Best choice: let


Object setup

val request = Request().apply {
    method = "GET"
    url = "/users"
}

Best choice: apply


Logging in a chain

val result = loadUsers()
    .also { println("Loaded ${it.size} users") }
    .filter { it.active }

Best choice: also


Build a value from several operations

val summary = users.run {
    val activeCount = count { it.active }
    "Active users: $activeCount"
}

Best choice: run


Group repeated calls on one object

val result = with(StringBuilder()) {
    append("A")
    append("B")
    append("C")
    toString()
}

Best choice: with


Avoid overusing scope functions

Scope functions are useful, but chaining too many can make code harder to read:

val result = user
    .also { println(it) }
    .let { transform(it) }
    .also { save(it) }
    .run { toDto() }

Sometimes plain code is clearer:

println(user)

val transformed = transform(user)
save(transformed)

val result = transformed.toDto()

A good guideline is: use scope functions when they make intent clearer, not just to make code shorter.

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 use the “this” keyword in Java?

Every instance method has a variable with the name this that refers to the current object for which the method is being called. You can refer to any member of the current object from within an instance method or a constructor by using this keyword.

Each time an instance method is called, the this variable is set to reference the particular class object to which it is being applied. The code in the method will then relate to the specific members of the object referred to by this keyword.

package org.kodejava.basic;

public class RemoteControl {
    private String channelName;
    private int channelNum;
    private int minVolume;
    private int maxVolume;

    RemoteControl() {
    }

    RemoteControl(String channelName, int channelNum) {
        // use "this" keyword to call another constructor in the 
        // same class
        this(channelName, channelNum, 0, 0);
    }

    RemoteControl(String channelName, int channelNum, int minVol, int maxVol) {
        this.channelName = channelName;
        this.channelNum = channelNum;
        this.minVolume = minVol;
        this.maxVolume = maxVol;
    }

    public static void main(String[] args) {
        RemoteControl remote = new RemoteControl("ATV", 10);

        // when the following line is executed, the variable in
        // changeVolume() is referring to remote object.
        remote.changeVolume(0, 25);
    }

    public void changeVolume(int x, int y) {
        this.minVolume = x;
        this.maxVolume = y;
    }
}