ZetCode

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.

Main.java
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.

Main.java
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.

Main.java
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(...).

Main.java
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:

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 Record API

Java Language Specification - record constructors

In this article we have worked with canonical constructors in Java records.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Java tutorials.