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