A record declares a fixed set of components and supplies component accessors, construction, value-based equality, hashing, and a textual representation.
Java records: value carriers and defensive copies
Java 17+. Use a JDK that supports this release.
Validate at construction
A receipt batch should always have an ID and an owned list of receipt IDs. The compact constructor rejects a blank batch ID and copies the incoming list. Its accessor returns that copied, unmodifiable list.
Final component references do not freeze the objects they reference. List.copyOf prevents structural changes to this copied list; it does not copy a mutable object stored as an element. String elements make the ownership rule straightforward here.
Records are suitable when their component list is the value contract. They are less suitable when callers should never observe internal components or when an object needs an evolving identity separate from its data.
Equality follows the components
Two instances with equal components compare equal by the generated equals implementation. For an array component, array equality remains reference equality unless you override the relevant behavior. A record does not turn an array into a value-equality list.
Do not store sensitive components and then casually log the generated toString. The generated representation exposes component values. Decide redaction at the logging boundary.
The lesson targets Java 17 even though records became permanent earlier. That baseline lets the same project use the sealed-type lesson without preview flags.
Working program
import java.util.ArrayList;
import java.util.List;
public class ReceiptBatches {
record ReceiptBatch(String id, List<String> receiptIds) {
ReceiptBatch {
if (id == null || id.isBlank()) throw new IllegalArgumentException("Batch ID required");
receiptIds = List.copyOf(receiptIds);
}
}
public static void main(String[] args) {
List<String> imported = new ArrayList<>(List.of("r-41", "r-52"));
ReceiptBatch batch = new ReceiptBatch("batch-8", imported);
imported.clear();
System.out.println(batch.receiptIds().size());
System.out.println(batch.equals(new ReceiptBatch("batch-8", List.of("r-41", "r-52"))));
}
}Output
2
trueCost and design choices
Copying n list references takes O(n) time and O(n) storage. The immutable String elements are shared. Value equality for two lists can require O(n) element comparisons; a record is not guaranteed constant-time equality.
List.copyOf rejects null elements. If null has a domain meaning, choose a different representation and state that contract instead of silently dropping values.
Common Mistakes
- Do not claim records are deeply immutable.
- Do not assume array components use content equality.
- Do not log every generated component representation without checking data sensitivity.
