Java canonical constructor
last modified August 19, 2026
In this article we explain the canonical constructor in Java record types. A canonical constructor receives exactly the record components and is the natural place to validate or normalize the data used to create a record.
A record is a concise class-like type for immutable data. For each component, the compiler creates a private final field, a public accessor, and a parameter in the record's canonical constructor.
record User(String name, String occupation) {
}
The declaration above has a canonical constructor whose signature is
User(String, String). If we do not write that constructor, the
compiler supplies it and initializes the components directly from its arguments.
Java provides two ways to write a canonical constructor: a compact canonical constructor with no parameter list, and a normal canonical constructor with an explicit parameter list.
The compiler still generates equals, hashCode, and
toString. The constructor only changes how the incoming component
values are checked or prepared before they become part of the record.
Compact canonical constructor
The compact form is usually the clearest way to enforce an invariant. It uses the component names as constructor parameters, but the parameter list is not written. At the end of the constructor, Java assigns the possibly changed parameter values to the record components.
import java.util.Objects;
void main() {
var u = new User(" John Doe ", " gardener ");
System.out.println(u);
}
record User(String name, String occupation) {
User {
name = Objects.requireNonNull(name, "name").trim();
occupation = Objects.requireNonNull(occupation, "occupation").trim();
if (name.isEmpty() || occupation.isEmpty()) {
throw new IllegalArgumentException("record components must not be empty");
}
}
}
The constructor is canonical because it has one parameter for every record component, in the same order and with the same types. The compact syntax starts with the record name followed by a block.
User {
name = Objects.requireNonNull(name, "name").trim();
occupation = Objects.requireNonNull(occupation, "occupation").trim();
}
The names name and occupation refer to the implicit
constructor parameters. We normalize them by removing leading and trailing
whitespace. Assignments to those parameters affect the values that are stored
in the record.
if (name.isEmpty() || occupation.isEmpty()) {
throw new IllegalArgumentException("record components must not be empty");
}
The constructor rejects invalid state before an instance can be created. If the constructor throws an exception, no partially initialized record is returned.
var u = new User(" John Doe ", " gardener ");
System.out.println(u);
The values printed by the program are normalized by the constructor.
$ java Main.java User[name=John Doe, occupation=gardener]
Validation example
A canonical constructor is useful when every instance must satisfy a rule. The following record accepts only positive dimensions.
void main() {
var screen = new Size(1920, 1080);
System.out.println(screen.width() + " x " + screen.height());
try {
new Size(0, 1080);
} catch (IllegalArgumentException ex) {
System.out.println(ex.getMessage());
}
}
record Size(int width, int height) {
Size {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("dimensions must be positive");
}
}
}
The compact constructor does not need assignments such as
this.width = width. Record fields are final and are initialized
automatically after the compact constructor body finishes normally.
$ java Main.java 1920 x 1080 dimensions must be positive
Explicit canonical constructor
The normal form writes the constructor parameter list explicitly. Its parameter types and order must match the record header. Unlike the compact form, the constructor explicitly assigns every component to its corresponding field.
import java.util.Objects;
void main() {
var email = new Email(" ADMIN@EXAMPLE.COM ");
System.out.println(email.address());
}
record Email(String address) {
Email(String address) {
this.address = Objects.requireNonNull(address, "address")
.trim().toLowerCase();
}
}
The constructor has the same component list as the record: one
String address parameter. The assignment to
this.address is required in this explicit form.
$ java Main.java admin@example.com
Canonical and non-canonical constructors
An additional constructor can provide a convenient way to create a record, but
it is not canonical when its parameters do not exactly match the record
components. It must delegate to the canonical constructor with
this(...).
void main() {
var p = new Point(10, 20);
var origin = new Point();
System.out.println(p);
System.out.println(origin);
}
record Point(int x, int y) {
Point {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("coordinates must not be negative");
}
}
Point() {
this(0, 0);
}
}
Point() is a non-canonical constructor because it has no
parameters. It delegates to the canonical constructor, so the same validation
rule is applied to both construction paths.
Point() {
this(0, 0);
}
An overloaded record constructor must delegate before doing anything else. A constructor that does not delegate to the canonical constructor is rejected by the compiler.
Common rules
Keep the following rules in mind when writing a canonical constructor:
- A compact canonical constructor has no parameter list and uses the record components as implicit parameters.
- A normal canonical constructor must declare the same component types in the same order.
- Use the constructor to validate and normalize values that define the record's invariant.
- Do not assign record fields in a compact constructor; Java performs those assignments after the body completes.
- Additional constructors must delegate to another constructor, usually the canonical one.
Conclusion
The canonical constructor is the creation boundary for a record. The compact form is concise and works well for validation, while the explicit form is useful when the parameter list or field assignments need to be visible. By enforcing the record's invariant there, every instance created through the canonical constructor starts in a valid state.
Source
Java Language Specification - record constructors
In this article we have worked with canonical constructors in Java records.
Author
List all Java tutorials.