Compact Constructors in Records

Naveen AnamaneniUpdated · 4 min read

A record generates its constructor for you, but that constructor accepts anything: a range from 9 down to 2, a blank name, a list the caller can keep changing. A compact constructor lets you add checks and clean-ups without rewriting the assignments. You write only the extra logic; the compiler adds the field assignments at the end.

Validating and Normalizing

Compact.java▶ Try it
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
 
public class Compact {
    record Range(int lo, int hi) {
        Range {
            if (lo > hi) {
                throw new IllegalArgumentException("lo " + lo + " > hi " + hi);
            }
        }
 
        Range(int single) { this(single, single); }
 
        int length() { return hi - lo + 1; }
    }
 
    record Student(String name, List<String> subjects) {
        Student {
            name = Objects.requireNonNull(name).strip();
            subjects = List.copyOf(subjects);
        }
    }
 
    public static void main(String[] args) {
        System.out.println(new Range(3, 7).length());
        System.out.println(new Range(5));
        try {
            new Range(9, 2);
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
 
        List<String> list = new ArrayList<>(List.of("Physics"));
        Student s = new Student("  Advitha ", list);
        list.add("Gaming");
        System.out.println(s);
        try {
            s.subjects().add("Chemistry");
        } catch (UnsupportedOperationException e) {
            System.out.println("subjects are read-only");
        }
    }
}
Output
5
Range[lo=5, hi=5]
lo 9 > hi 2
Student[name=Advitha, subjects=[Physics]]
subjects are read-only

How it works

  1. Range { ... } is a compact constructor: the record's name with no parameter list. The parameters lo and hi are available inside it.
  2. It validates: a reversed range throws, so an invalid Range can never exist.
  3. After the body, the compiler adds this.lo = lo; this.hi = hi; automatically.
  4. In Student, the body reassigns the parameters: name is stripped and subjects is replaced by an unmodifiable copy. The compiler then stores those new values. That's how a record becomes deeply immutable: the caller's later list.add("Gaming") doesn't reach the record.
  5. Range(int single) is an extra constructor. Every non-canonical constructor in a record must start by calling another with this(...).

Compact vs Canonical

The canonical constructor is the one whose parameters match the components exactly. You can write it in full, or in the compact form:

Compact formFull canonical form
Range { if (lo > hi) throw ...; }Range(int lo, int hi) { if (lo > hi) throw ...; this.lo = lo; this.hi = hi; }
No parameter listParameter names and types must match the components
Field assignments added by the compilerYou assign every field yourself
Writing this.lo = lo is an errorRequired

In a compact constructor, assigning a field directly fails:

Compiler output
R4.java:4: error: cannot assign a value to final variable lo
        this.lo = lo;
            ^
1 error

Common Mistakes

Writing this.name = ... in a compact constructor. Reassign the parameter instead: name = ....

Forgetting to copy mutable components. Without List.copyOf, the record stays only shallowly immutable.

Extra constructors that don't call this(...). Every one must delegate to the canonical constructor.

Practice

  1. Easy: Write record Percentage(double value) with a compact constructor that rejects values below 0 or above 100.
  2. Harder: Write record Team(String name, List<String> members) that strips and capitalizes the name, rejects an empty member list, and stores an unmodifiable copy of the members.

Quick check: Compact constructors

4 questions
1How is a compact constructor written?
2Who assigns the fields in a compact constructor?
3How do you store a cleaned-up value?
4What must an extra record constructor do first?

Key Takeaways

Was this tutorial helpful?

Lesson 175 of 482