You subclass Person into Employee. Ages for employees must sit between 18 and 67. You want to reject a bad age before calling super(...) — fail fast, no half-built superclass state.
For decades Java said no: the first statement had to be super(...) or this(...). So teams invented a private static helper and stuffed the check into the argument list.
Employee(String name, int age) {
super(name, verifyAge(age)); // the hack
}
private static int verifyAge(int age) {
if (age < 18 || age > 67)
throw new IllegalArgumentException("age out of range: " + age);
return age;
}
It works. It is also noisy and easy to miss when you skim the constructor.
Flexible constructor bodies let a constructor have a prologue (statements before super / this) and an epilogue (statements after). The prologue can validate arguments, prepare values for the next constructor, and assign this class’s fields — without treating the object as fully constructed.
The problem the old rule created
Two pain points show up again and again:
- Fail-fast validation — you want to throw before the superclass constructor runs, not after it has already allocated and initialized superclass fields.
- Subclass integrity — a superclass constructor that calls an overridable method can see subclass fields still at default (
null,0,false) because the subclass constructor has not run its assignments yet.
The static-helper pattern fixes (1) awkwardly. It does nothing for (2). Flexible constructor bodies address both.
When flexible constructor bodies shipped
| Release | Status | Spec |
|---|---|---|
| Java 22 | Preview (“Statements before super()”) | JEP 447 |
| Java 23 | Second preview | JEP 482 |
| Java 24 | Third preview | JEP 492 |
| Java 25 LTS | Standard feature | JEP 513 |
Use Java 25+. No --enable-preview. The final feature matches the last preview — no API surprise at finalization.
Mental model: prologue and epilogue
SubclassCtor(...) {
// prologue — early construction context
super(...); // or this(...)
// epilogue — instance is usable as usual
}
| Phase | When it runs | What you can do |
|---|---|---|
| Prologue | Before super(...) / this(...) | Validate args; compute values; assign this class’s fields that have no field initializer |
| Epilogue | After that call returns | Normal constructor code: call instance methods, read fields, finish setup |
If you omit an explicit super / this, the compiler still inserts super() at the start — the whole body is then epilogue, same as before.
Invocation order across a hierarchy: prologues run bottom-up, then epilogues run top-down. That is how a subclass can initialize its own fields before a superclass constructor body (or an override it calls) can observe them.
Lab: fail fast without the helper
Before (validation after super — wasteful if age is bad):
class Person {
final String name;
final int age;
Person(String name, int age) {
if (age < 0)
throw new IllegalArgumentException("age");
this.name = name;
this.age = age;
}
}
class Employee extends Person {
Employee(String name, int age) {
super(name, age); // runs even when age is 12
if (age < 18 || age > 67)
throw new IllegalArgumentException("age out of range: " + age);
}
}
After (prologue validates, then delegates):
class Employee extends Person {
Employee(String name, int age) {
if (age < 18 || age > 67)
throw new IllegalArgumentException("age out of range: " + age);
super(name, age);
}
}
No private static helper. The check sits where a reader expects it.
Lab: prepare arguments, then call super
When building the superclass args takes more than one expression:
class ColoredBox extends Box {
ColoredBox(int width, int height, String colorName) {
Objects.requireNonNull(colorName, "colorName");
Color color = Color.decode(colorName); // prologue computation
super(width, height, color);
}
}
Anything you could have jammed into super(width, height, Color.decode(...)) can live as clear statements in the prologue — still without reading this as a finished object.
Lab: initialize subclass fields before super
When a superclass constructor calls an overridable method, subclass fields used to stay at defaults until after super returned. Assign them in the prologue instead:
class Person {
final String name;
Person(String name) {
this.name = name;
describe(); // may dispatch to subclass override
}
void describe() {
System.out.println("Person " + name);
}
}
class Employee extends Person {
final String officeId;
Employee(String name, String officeId) {
Objects.requireNonNull(officeId, "officeId");
this.officeId = officeId; // before super — field must lack an initializer
super(name);
}
@Override
void describe() {
System.out.println("Employee " + name + " @ " + officeId);
}
}
new Employee("Ada", "CAM-FORA") can now print a non-null office id from describe(), because the prologue ran first.
Note: Calling overridable methods from constructors is still a smell (Effective Java Item 19). Flexible bodies reduce the damage when a superclass already does it; they do not make the pattern good design. Prefer not to leak this from a constructor at all.
What the prologue cannot do
The prologue (and super / this argument lists) sit in an early construction context:
| Allowed | Forbidden |
|---|---|
| Throw on bad args | return (with or without a value) |
| Call static methods / use locals | Call instance methods on this |
| Assign fields declared in this class with no field initializer | Read fields of this (including after you just assigned them, in ways that count as a read of the instance) |
Compute values passed to super / this | Assign superclass fields; touch fields that already have = ... initializers |
Rough rule: treat the prologue like “almost static,” with one exception — simple assignments to your own uninitialized fields.
When they win vs when to skip
| Need | Prefer |
|---|---|
Fail-fast checks before super | Prologue if + throw |
Non-trivial prep of super args | Locals in the prologue |
| Subclass fields visible if superclass code runs early | Assign those fields in the prologue |
| Complex multi-step creation | Factory or builder — not a mega-constructor |
| Shared setup across many ctors | this(...) chaining; keep one canonical constructor |
Do not turn every constructor into a mini-framework. Flexible bodies remove an artificial restriction; they do not invent a new place for business workflows.
Gotchas
Fields with initializers
You cannot assign this.x in the prologue if x already has a field initializer (int x = 0;). Drop the initializer and assign in the prologue, or assign in the epilogue after super.
Records
Record compact constructors are a different feature. Flexible constructor bodies apply to ordinary class (and enum) constructors. Keep records for data carriers; use this JEP when inheritance and super(...) are in play.
Exactly one next constructor
You still invoke either super(...) or this(...) once (explicitly or via the implicit super()). The prologue does not replace chaining — it sits in front of it.
Epilogue return
Bare return; is allowed in the epilogue. return is a compile-time error in the prologue. You still cannot return a value from a constructor.
Reading this too early
Even after assigning a field in the prologue, you must not treat the object as constructed: no instance method calls, no passing this to other code that expects a finished instance.
Cheat sheet
Ctor(...) {
// prologue: validate, compute, assign own fields (no initializers)
super(...); // or this(...)
// epilogue: normal instance code
}
Java 25+ (JEP 513); no preview flag
Prologues run bottom-up; epilogues top-down
Good: fail-fast before super; prep args; early subclass field init
Avoid: mega-constructors; relying on overridable calls from super
Watch: fields with initializers; no instance calls in prologue
Do:
- Put fail-fast checks in the prologue so bad args never reach
super. - Assign subclass fields in the prologue when superclass code might observe them early.
- Keep heavy construction logic in factories when a constructor would become a script.
Don’t:
- Call instance methods or pass
thisout from the prologue. - Expect to assign fields that already have
= ...initializers beforesuper. - Treat flexible bodies as permission for superclass constructors to call overridable methods.
Pros and cons
Pros
- Readable fail-fast — no static-helper smuggling into
super(...) - Clear prologue / epilogue split for what runs before vs after the next constructor
- Stronger subclass integrity when fields must be set before superclass code runs
- Final in Java 25 — no preview tax
Cons
- Early construction rules are easy to trip over (no instance calls, initializer restrictions)
- Does not fix bad superclass design that leaks
thisor calls overrides - Requires Java 25+ for the standard language feature
- Temptation to stuff too much into constructors instead of factories
Wrap-up
Flexible constructor bodies (JEP 513) drop the “first statement must be super / this” rule. Use a prologue to validate, prepare arguments, and initialize this class’s fields; use the epilogue for ordinary post-super setup.
Start by deleting the verifyX static helpers that existed only to sneak checks into super(...). Keep constructors short. When Java’s ceremony still feels heavy for scripts and demos, the next Java 25 story is compact source files and instance main — void main() without the boilerplate class shell.