Question 6 · Section 20

Can you override constructor in Record

Structured Java interview answer with junior, middle, and senior-level explanation.

Language versions: English Russian Ukrainian

🟢 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

  1. 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");
     }
    }
    
  2. 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]]