Can you override constructor in Record
Structured Java interview answer with junior, middle, and senior-level explanation.
🟢 Junior Level
Yes, but with restrictions. You can add your own constructor to a Record, but it must call the canonical constructor (via this(...)).
Two ways:
// Way 1: Full form
public record User(String name, int age) {
// Your own constructor — must call this(...)
public User(String name) {
this(name, 0); // call canonical constructor
}
}
// Way 2: Compact constructor (validation only)
public record User(String name, int age) {
public User {
if (age < 0) {
throw new IllegalArgumentException("Invalid age");
}
}
}
You cannot:
- ❌ Create a constructor that doesn’t call
this(...) - ❌ Add a constructor without calling canonical
🟡 Middle Level
How it works
1. Canonical constructor (auto-generated):
public record Point(int x, int y) {}
// Auto-generated:
public Point(int x, int y) {
this.x = x;
this.y = y;
}
2. Additional constructor:
public record Point(int x, int y) {
// Must call this(...) on the first line
public Point() {
this(0, 0); // ✅ OK
}
public Point(int value) {
this(value, value); // ✅ OK
}
// ❌ public Point(String s) { } — no this()!
}
3. Compact constructor (validation only):
public record Email(String value) {
// Compact constructor — does NOT contain signature
public Email {
if (value == null || !value.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
}
}
// Usage
Email e1 = new Email("test@example.com"); // ✅ OK
Email e2 = new Email("invalid"); // throws IllegalArgumentException
Common Mistakes
- Forgot to call
this(...):public record User(String name) { // ❌ compilation error — no this() call public User() { System.out.println("Created"); } // ✅ Correct public User() { this("Anonymous"); } } - Compact constructor with assignment:
public record User(String name) { // Compact constructor CAN reassign parameters: // name = name.toUpperCase() — valid in Java 16+. // Assignment happens before implicit field assignment. public User { name = name.toUpperCase(); // OK in Java 16+ } }
Practical Application
1. Validation:
public record Money(BigDecimal amount, Currency currency) {
public Money {
Objects.requireNonNull(currency, "Currency cannot be null");
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Negative amount");
}
}
}
2. Data normalization:
public record Email(String value) {
public Email {
value = value.toLowerCase().trim(); // normalization
}
}
3. Factory constructors:
public record Range(int min, int max) {
public Range(int value) {
this(value, value);
}
public Range(String rangeStr) {
// parsing "1-100" -> min=1, max=100
String[] parts = rangeStr.split("-");
this(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]));
}
}
🔴 Senior Level
Internal Implementation
Compiler generates:
// Source code
public record User(String name, int age) {
public User {
if (age < 0) throw new IllegalArgumentException();
}
public User(String name) {
this(name, 0);
}
}
// Desugared:
public final class User extends Record {
private final String name;
private final int age;
// Canonical constructor + compact validation
public User(String name, int age) {
if (age < 0) throw new IllegalArgumentException();
this.name = name;
this.age = age;
}
// Additional constructor
public User(String name) {
this(name, 0);
}
}
Architectural Trade-offs
Compact constructor vs full constructor:
| Approach | Pros | Cons |
|---|---|---|
| Compact | Short, validation only | Cannot change parameters |
| Full | Flexibility, can change parameters | More code, risk of bugs |
Edge Cases
1. Constructor chaining:
public record Config(String host, int port, String protocol) {
public Config(String host, int port) {
this(host, port, "http");
}
public Config(String host) {
this(host, 8080);
}
public Config() {
this("localhost");
}
public Config {
if (port < 1 || port > 65535) {
throw new IllegalArgumentException("Invalid port");
}
}
}
2. Normalization with mutable objects:
public record User(List<String> emails) {
public User {
// Defensive copy for mutable collections
emails = new ArrayList<>(emails);
}
}
3. Constructor with exception:
public record JsonNode(String json) {
public JsonNode {
try {
// JSON validation
Json.parse(json);
} catch (JsonParseException e) {
throw new IllegalArgumentException("Invalid JSON", e);
}
}
}
Performance
Record constructors:
- Compact constructor: inline validation
- Full constructor: delegation (one call)
- Difference negligible (< 1 ns)
- JIT inlines both approaches
Production Experience
DTO with validation:
public record CreateUserRequest(
@NotBlank String name,
@Email String email,
@Min(18) int age
) {
public CreateUserRequest {
// Additional validation after Bean Validation
if (name.length() > 100) {
throw new IllegalArgumentException("Name too long");
}
}
}
Domain objects:
public record OrderId(String value) {
private static final Pattern UUID_PATTERN =
Pattern.compile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$");
public OrderId {
if (!UUID_PATTERN.matcher(value).matches()) {
throw new IllegalArgumentException("Invalid UUID format");
}
value = value.toLowerCase(); // normalization
}
public static OrderId generate() {
return new OrderId(UUID.randomUUID().toString());
}
}
Best Practices
// ✅ Compact constructor for validation
public record Email(String value) {
public Email {
Objects.requireNonNull(value);
if (!value.contains("@")) throw new IllegalArgumentException();
}
}
// ✅ Full constructor for convenience
public record Point(int x, int y) {
public Point() { this(0, 0); }
public Point(int v) { this(v, v); }
}
// ❌ Don't mutate fields after assignment
// ❌ Don't try to bypass validation
// ❌ Don't add side effects to constructor
🎯 Interview Cheat Sheet
Must know:
- You can add your own constructor to Record, but it must call canonical via
this(...) - Compact constructor — form without signature, only for validation and normalization
- Compact constructor is inlined into canonical before field assignment
- In compact constructor you can modify parameters before assignment:
name = name.toUpperCase() - Checked exceptions cannot be thrown from compact constructor (canonical doesn’t declare throws)
- You can have multiple additional constructors + one compact constructor
Common follow-up questions:
- How does compact constructor differ from regular? — Compact has no signature, validation only; regular must call
this(...) - Can you normalize data in constructor? — Yes, in compact you can:
name = name.trim() - What happens if constructor doesn’t call this(…)? — Compilation error: “constructor must call this(…)”
- Can you throw checked exception from compact constructor? — No, must wrap in unchecked
Red flags (DO NOT say):
- ❌ “Compact constructor assigns fields via this.field” — Uses parameter name, not this.field
- ❌ “You can create constructor without calling this()” — Calling canonical constructor is mandatory
- ❌ “Compact constructor can add field” — Cannot add instance field
- ❌ “Constructor can throw checked exception” — Canonical constructor doesn’t declare throws
Related topics:
- [[1. What is Record in Java and since which version are they available]]
- [[4. Can you add additional methods to a Record]]
- [[7. What is compact constructor in Record]]
- [[9. Are Record fields final]]