Java 25 Flexible Constructor Bodies with Examples

Introduction Since Java 1.0, a constructor had to start with a call to another constructor - super(...) or this(...) - as its very first statement, with nothing before it. That rule guaranteed the superclass was initialized before the subclass, but it was stricter than needed, and it got in the way: arguments could not be validated before being passed to the superclass, values could not be computed in local variables, and subclass fields could not be set before the superclass constructor ran. After previews in Java 22, 23 and 24, Java 25Click to read tutorial: Overview of New Features in Java 25 (JEP 513) finalized flexible constructor bodies, which allow statements before super(...) and this(...). This tutorial explains them with examples - validating arguments, preparing arguments, initializing fields early, and the rules for the code before the constructor call. Validating arguments before super() Suppose a PositiveAmount class extends a general Amount class and must reject negative values. Before Java 25, the check could only come after super(value) - so the superclass did its work with an invalid value first - or be squeezed into a static helper method called inside the super(...) arguments. Now it can simply come first -
Validating before calling the superclass constructor
//ValidateBeforeSuper.java
public class ValidateBeforeSuper {
  static class Amount {
    final long cents;
    Amount(long cents) {
      this.cents = cents;
      System.out.println("  Amount constructor ran with " + cents);
    }
  }

  static class PositiveAmount extends Amount {
    PositiveAmount(long cents) {
      if (cents <= 0) {                                     // allowed before super() since Java 25
        throw new IllegalArgumentException("amount must be positive: " + cents);
      }
      super(cents);
    }
  }

  public static void main(String[] args) {
    System.out.println("Creating 500:");
    new PositiveAmount(500);
    System.out.println("Creating -5:");
    try {
      new PositiveAmount(-5);
    } catch (IllegalArgumentException e) {
      System.out.println("  Rejected before Amount was constructed: " + e.getMessage());
    }
  }
}
 OUTPUT of the above code
Creating 500:
  Amount constructor ran with 500
Creating -5:
  Rejected before Amount was constructed: amount must be positive: -5
For the invalid value, the Amount constructor never ran - the object failed fast, before any superclass code saw bad data.
Preparing arguments The code before super(...) or this(...) can use local variables to compute the arguments, instead of cramming the computation into a single expression -
Computing arguments in local variables
//PrepareArguments.java
public class PrepareArguments {
  record Range(int low, int high) { }

  static class Rectangle {
    final int width, height;
    Rectangle(int width, int height) { this.width = width; this.height = height; }
    public String toString() { return width + "x" + height; }
  }

  static class Square extends Rectangle {
    Square(String spec) {                                   // e.g. "side=12"
      String[] parts = spec.split("=");
      if (parts.length != 2 || !parts[0].equals("side")) {
        throw new IllegalArgumentException("expected side=<n>, got " + spec);
      }
      int side = Integer.parseInt(parts[1].strip());
      super(side, side);
    }
  }

  public static void main(String[] args) {
    System.out.println(new Square("side=12"));
  }
}
 OUTPUT of the above code
12x12
Initializing fields early The most subtle benefit concerns a long-standing trap. If a superclass constructor calls a method which a subclass overrides, the overriding method runs before the subclass's fields are initialized, and sees them as null or 0. With flexible constructor bodies, a subclass can assign its own fields before calling super(...) -
Assigning a field before the superclass constructor runs
//EarlyFieldInit.java
public class EarlyFieldInit {
  static class Widget {
    Widget() {
      System.out.println("Widget constructor calls describe(): " + describe());
    }
    String describe() { return "a widget"; }
  }

  static class LabelOld extends Widget {
    private final String text;
    LabelOld(String text) {
      super();                         // describe() runs here, before text is assigned
      this.text = text;
    }
    @Override String describe() { return "label '" + text + "'"; }
  }

  static class LabelNew extends Widget {
    private final String text;
    LabelNew(String text) {
      this.text = text;                // assigned before super() - allowed since Java 25
      super();
    }
    @Override String describe() { return "label '" + text + "'"; }
  }

  public static void main(String[] args) {
    new LabelOld("OK");
    new LabelNew("OK");
  }
}
 OUTPUT of the above code
Widget constructor calls describe(): label 'null'
Widget constructor calls describe(): label 'OK'
LabelOld shows the old trap: the overridden describe() runs inside the Widget constructor and sees text as null - even though it is a final field. LabelNew assigns the field first, so the object is consistent by the time any superclass code runs. (This is also how records and the planned value classes of Project Valhalla are meant to initialize their fields.)
The rules: the early construction context The statements before super(...) or this(...) form the prologue of the constructor, and run in an early construction context, where the object does not fully exist yet. In the prologue, code may -
  • use the constructor's parameters and declare local variables, call static methods, and throw exceptions, and
  • assign fields of the class being constructed which have no initializer.
It may not use the object under construction in any other way - no reading fields, no calling instance methods, no passing this anywhere, and no return -
What the prologue cannot do
//EarlyContextErrors.java
public class EarlyContextErrors {
  static class Base { Base(int x) { } }

  static class Child extends Base {
    int count;
    Child(int x) {
      int doubled = count * 2;      // cannot read a field before super()
      helper();                     // cannot call an instance method before super()
      super(x);
    }
    void helper() { }
  }
}
 OUTPUT on compiling the above code
EarlyContextErrors.java:8: error: cannot reference count before supertype constructor has been called
      int doubled = count * 2;      // cannot read a field before super()
                    ^
EarlyContextErrors.java:9: error: cannot reference helper() before supertype constructor has been called
      helper();                     // cannot call an instance method before super()
      ^
2 errors
These rules keep the guarantee that no code can observe a partly constructed object, while removing the restrictions which were not needed for it. Summary In this tutorial we saw how flexible constructor bodies, final in Java 25, allow statements before super(...) and this(...). We validated arguments before the superclass constructor ran, prepared arguments in local variables, and assigned a field before super() to avoid an overridden method seeing it uninitialized. Lastly, we went through the rules of the early construction context, with the compiler errors for breaking them. Next, read the scoped values tutorialClick to read tutorial: Java 25 Scoped Values Tutorial with Examples.