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.
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.
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.
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
}
}
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.
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
}
}
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
}
}