When a superclass constructor invokes an overridable method that a subclass has overridden to access its own fields, those fields may be accessed before they are initialized. In Java 25+, subclass constructors can now initialize fields in the constructor prologue—the area before the super() call—to ensure that any methods called during superclass construction observe a fully initialized state.

Why is this an issue?

Traditionally, the explicit constructor invocation (super(…​) or this(…​)) had to be the first statement in a constructor, forcing subclass field initialization to happen after the superclass was already constructed. If the superclass constructor calls an overridable method, the subclass implementation will see default values (such as null, 0, or false) for its fields instead of the values intended by the caller. This leads to subtle bugs, NullPointerExceptions, or inconsistent object states that are difficult to debug.

How to fix it

Move the initialization of subclass fields before the super() call. This takes advantage of flexible constructor bodies to ensure that the subclass state is established before the superclass constructor begins its execution. Alternatively, if the method in the superclass does not need to be overridden, mark it as final or private to prevent the issue entirely.

Code examples

Noncompliant code example

class Super {
    Super() {
        foo();
    }

    void foo() {
        System.out.println("Base logic");
    }
}

class Sub extends Super {
    final int x;

    Sub(int x) {
        super();
        this.x = x; // Noncompliant: x is uninitialized when foo is called by Super()
    }

    @Override
    void foo() {
        System.out.println(x); // Prints 0 instead of the value of x
    }
}

Compliant solution

class Super {
    Super() {
        foo();
    }

    void foo() {
        System.out.println("Base logic");
    }
}

class Sub extends Super {
    final int x;

    Sub(int x) {
        this.x = x; // Compliant: x is initialized in the prologue before super()
        super();
    }

    @Override
    void foo() {
        System.out.println(x); // Prints the expected value
    }
}

Alternatively, if the method in the superclass does not need to be overridden, it can be marked as final or private to prevent the issue entirely.

Noncompliant code example

class Super {
    Super() {
        foo();
    }

    void foo() {
        System.out.println("Base logic");
    }
}

class Sub extends Super {
    final int x;

    Sub(int x) {
        super();
        this.x = x; // Noncompliant: x is uninitialized when foo is called by Super()
    }

    @Override
    void foo() {
        System.out.println(x); // Prints 0 instead of the value of x
    }
}

Compliant solution

class Super {
    Super() {
        foo();
    }

    final void foo() {
        System.out.println("Base logic");
    }
}

class Sub extends Super {
    final int x;

    Sub(int x) {
        super();
        this.x = x; // Compliant: foo is final, so it cannot be overridden and will not access uninitialized fields
    }
}

Resources

Documentation