skip to content

Beyond the canonical constructor, what other constructors can a record declare, and what is the one rule they must follow?

level: middleimportance: should knowfreq 50%

answer

  1. Overloads allowed for defaults
  2. First statement must be this(...)
  3. Chain must reach the canonical constructor
  4. No field assignment, no super(...)
  5. Single validation gate

basics

~20 s

A record can have extra constructors with different parameter lists (overloads), for example to supply defaults. The rule: every non-canonical constructor must call another constructor with this(...) as its first statement, eventually reaching the canonical one.

solid answer

~50 s

Records can declare additional (non-canonical) constructors that overload the canonical one with different parameter lists — useful for supplying default values or alternative construction shapes. The strict rule is that every non-canonical constructor's first statement must be an explicit `this(...)` delegation to another record constructor, and that chain must eventually reach the canonical constructor. This is unlike ordinary classes, where a constructor may call `super(...)` or do field assignment directly; a record constructor may NOT assign the component fields itself (only the canonical constructor — or the compiler in the compact form — does that) and cannot call `super(...)` (records implicitly extend `java.lang.Record`). So all construction funnels through the canonical constructor, which keeps it the single point where validation and field assignment happen. A common pattern is a no-arg or partial constructor that calls `this(...)` to fill in defaults.

code

java · 12 lines
java
record User(String name, int age) {
    User { // canonical (compact) - the single validation point
        if (age < 0) throw new IllegalArgumentException("age < 0");
    }
    User(String name) { // non-canonical overload: default age
        this(name, 0);   // MUST delegate first; cannot assign fields here
    }
    // User(String name) { this.name = name; }  // illegal: no field assignment
    // User() { super(); }                       // illegal: no super(...) in a record
}

new User("Ann");      // -> User("Ann", 0), still runs the canonical checks

go deeper

for a junior

Knows a record can have more than one constructor for convenience/defaults.

for a middle

States the this(...)-first delegation rule and that only the canonical constructor assigns fields.

for a senior

Explains the single-validation-gate rationale, the no-super/no-field-assignment constraints, and when to prefer static factories.

for a principal

Designs record APIs balancing overloaded constructors vs static factories, ensuring all paths preserve invariants and considering forward-compatibility of the component set.

## Recap: the canonical constructor The **canonical constructor** matches the record's component list and is the one place component fields get assigned (either by you in the full form, or by the compiler in the compact form). Now, can a record have *other* constructors? ## Yes — non-canonical (overloaded) constructors A record may declare extra constructors whose parameter lists differ from the components. These are ordinary **overloads** (same name, different parameters), typically used to provide **defaults** or convenience shapes: ```java record Range(int lo, int hi) { Range(int hi) { // non-canonical: only a high bound this(0, hi); // MUST delegate to another constructor first } } new Range(10); // -> Range(0, 10) ``` ## The one rule: delegate with this(...) Every **non-canonical** constructor's **first statement must be `this(...)`** — an explicit delegation to another record constructor. That chain must ultimately reach the **canonical** constructor. Consequences: - A non-canonical record constructor **may not assign the component fields** itself. Field assignment is the canonical constructor's job (or the compiler's, in the compact form). Trying `this.lo = lo;` in a non-canonical constructor is a compile error. - A record constructor **cannot call `super(...)`**. Records implicitly extend `java.lang.Record`, and the language manages that for you; you only ever chain with `this(...)`. Contrast with a normal class, where a constructor's first statement may be `super(...)`, `this(...)`, or nothing (an implicit `super()`), and where the constructor is free to assign fields. Records are deliberately stricter. ## Why this design Funneling all construction through the canonical constructor means **validation and field assignment live in exactly one place**. No matter which overload a caller uses, the data still passes through the canonical constructor's checks before it becomes object state. This preserves the record's invariants and keeps the derived `equals`/`hashCode`/`toString` consistent. ## A note on static factories Because records can't return subtypes or cache instances from a constructor, teams often add **static factory methods** (`static Range of(...) { ... }`) alongside the constructors for naming and flexibility. Static factories are not constructors, so they aren't bound by the `this(...)`-first rule, but they typically end by calling a constructor anyway. ## Summary - Extra constructors are allowed and are plain overloads. - Each must start with `this(...)` and chain to the canonical constructor. - They can't assign fields or call `super(...)`. - Net effect: the canonical constructor is the single validation/assignment gate.

  • Why must a non-canonical record constructor start with this(...)?
    So all construction funnels through the canonical constructor, which is the only place component fields are assigned and validation runs. It guarantees every construction path enforces the same invariants.
  • Can a record constructor call super(...)?
    No. Records implicitly extend java.lang.Record and the language manages the superclass call. Record constructors may only chain with this(...) to another record constructor.

Think of the canonical constructor as the only door into a vault. Side entrances (other constructors) exist for convenience, but each one just leads you down a hallway (this(...)) to that same single door — you can never reach the vault's interior any other way.

saying these in an interview costs you the question

  • Assigning component fields inside a non-canonical constructor (compile error).
  • Calling super(...) from a record constructor.
  • Believing records can only ever have the canonical constructor and no overloads.

context