Can You Override Constructor in Record
Any secondary constructor must delegate initialization through this(...) until it reaches the canonical constructor. Direct invocation of super(...) is forbidden in records.
🟢 Junior Level
Yes, you can. In Java, records support three constructor forms:
- Compact Constructor: A specialized constructor syntax without parameter parentheses, designed for validation and normalization:
public record User(String name, int age) { public User { if (age < 0) { throw new IllegalArgumentException("Age cannot be negative: " + age); } name = name.trim(); // normalization } } - Explicit Canonical Constructor: A full declaration matching all components in the record header:
public record Point(int x, int y) { public Point(int x, int y) { this.x = x; this.y = y; } } - Secondary (Overloaded) Constructors: Constructors accepting alternative parameter sets that must explicitly delegate to the canonical constructor via
this(...)on the first line:public record User(String name, int age) { public User(String name) { this(name, 18); // Mandatory this(...) invocation } }
Core Invariant
Any secondary constructor must delegate initialization through this(...) until it reaches the canonical constructor. Direct invocation of super(...) is forbidden in records.
🟡 Middle Level
1. Internal Mechanics of the Compact Constructor
The compact constructor is a syntax unique to Record. It omits the parameter list: public User { ... }.
What the compiler does under the hood:
- Implicitly passes parameters corresponding to the header components (
name,age). - Executes user validation and normalization logic.
- Automatically appends final field assignments at the very end of the method:
this.name = name; this.age = age;
public record NormalizedEmail(String value) {
public NormalizedEmail {
Objects.requireNonNull(value, "Email must not be null");
// 'value' here is the implicit method parameter, not the field!
value = value.toLowerCase().trim();
// The compiler automatically appends: this.value = value;
}
}
[!WARNING] Inside a compact constructor, accessing fields via
this.fieldis prohibited! Writingthis.value = value;triggers a compilation error: “Cannot assign a value to final variable value”.
2. Defensive Copying in Constructors
If a record component is a mutable collection or array, initialization should create an unmodifiable defensive copy:
public record Team(String name, List<String> members) {
public Team {
Objects.requireNonNull(name);
// Create an unmodifiable defensive copy:
members = List.copyOf(members);
}
}
🔴 Senior Level
Java Language Specification Rules (JLS §8.10.4)
The Java Language Specification enforces strict constraints on record constructors:
1. Mutually Exclusive: Compact vs. Explicit Canonical
A record cannot declare both a compact constructor and an explicit canonical constructor:
public record Point(int x, int y) {
public Point { ... } // Compact constructor
// ❌ Compilation error: duplicate method Point(int, int)
public Point(int x, int y) { ... }
}
The compact constructor is compiled directly into the canonical constructor bytecode. Declaring both creates duplicate methods with identical signatures.
2. Access Modifier Constraints
The access modifier of the canonical or compact constructor cannot be more restrictive than the record class itself:
public record User(String name) {
// ❌ Compilation error: canonical constructor cannot have more restrictive access than the record class
private User(String name) {
this.name = name;
}
}
If the class is public, the canonical constructor must be public.
However, secondary constructors can declare any access modifier, including private:
public record Token(String value) {
// Canonical constructor is implicitly public
// Secondary constructor can be private:
private Token(byte[] raw) {
this(Base64.getEncoder().encodeToString(raw));
}
public static Token fromBytes(byte[] raw) {
return new Token(raw);
}
}
3. Exception Throws Clause Restrictions
- A compact constructor cannot declare a
throwsclause:public record FileData(String path) { // ❌ Compilation error: throws clause not allowed on compact constructor public FileData throws IOException { ... } }Validation failures must be thrown as
RuntimeExceptionsubclasses (IllegalArgumentException,NullPointerException). - An explicit canonical constructor can declare
throws CheckedException, but this is considered an anti-pattern because it disrupts functional instantiation and deserialization.
4 Tricky Questions
1. Can a Record declare both a compact constructor and an explicit canonical constructor simultaneously?
Answer:
No, this causes a compilation error.
A compact constructor is simply an alternative syntax for the canonical constructor. During bytecode generation, the compiler translates the compact constructor into a standard constructor with signature (ComponentType1, ComponentType2, ...). Declaring both creates two constructors with identical signatures, failing with a duplicate method compiler error.
2. Can the canonical constructor of a Record have a more restrictive access modifier than the Record class itself (e.g. private constructor on a public record)?
Answer:
No, it cannot.
Under JLS §8.10.4, the canonical constructor cannot be more restrictive than the record class. For a public record, the canonical constructor must be public. Records are transparent data carriers: any code with visibility to the record type must be able to construct it from its components.
However, secondary (overloaded) constructors can have more restrictive access modifiers, including private.
3. Is it permitted to access fields via this.field inside a compact constructor (e.g. this.x = 10)?
Answer:
No, accessing fields via this. inside a compact constructor is a compilation error.
Inside a compact constructor, user code executes before the final fields are assigned. Component identifiers (x, y) in the compact constructor refer to implicit local parameters, not instance fields. You can reassign parameters (x = Math.max(0, x);), and the compiler appends this.x = x; at the end. Attempting to directly write or read this.x fails compilation.
4. Can a secondary constructor in a Record call super(...) instead of this(...)?
Answer:
No, absolutely not.
A secondary record constructor must invoke another constructor of the same record via this(...) on its very first line. Direct calls to super(...) are prohibited. The only superclass of any record is java.lang.Record, whose constructor is invoked exclusively and automatically by the canonical constructor.
🎯 Interview Cheat Sheet
Summary in 60 Seconds
- Records support 3 constructor forms: Compact (
public User { ... }), Explicit Canonical (public User(String name, int age) { ... }), and Secondary (public User(String name) { this(name, 0); }). - Compact Constructor: Omits parameter parentheses; used for validation and normalization; compiler generates
this.field = paramassignments automatically. - Secondary Constructors: Must delegate via
this(...)on their first line; cannot callsuper(...). - Exclusivity: Cannot declare both a compact constructor and an explicit canonical constructor.
- Accessibility: Canonical/compact constructor cannot be more restrictive than the record class.
Common Interview Traps
- Trying to declare both compact and canonical constructors in the same record.
- Writing
this.field = valueinside a compact constructor (forbidden). - Declaring a
throwsclause on a compact constructor. - Forgetting that secondary constructors must delegate via
this(...).