A compact record constructor receives component parameters and implicitly assigns them to fields after its body completes. It can reject or normalize values before that assignment.
Java compact record constructors: normalize before field assignment
Operational contract
The sample strips surrounding whitespace from a shipment identifier, rejects a blank value, and limits mass to 47,000 grams. Assigning a new value to the constructor parameter changes what the implicit field assignment stores. The record is only shallowly immutable; if a component holds a mutable collection or array, copy it on entry and control what its accessor returns. Validation in the canonical constructor gives every ordinary construction path the same invariant. Deserialization and reflection still require separate trust considerations.
Failure case
A carrier sends ' parcel-82 ' with a mass of 23,000 grams. The stored record ID is 'parcel-82'. A blank ID or 47,001-gram mass fails before a record exists, preventing downstream code from quietly accepting an invalid shipment.
Java code
public class ShipmentMassPolicy {
public record ShipmentMass(String shipmentId, int grams) {
public ShipmentMass {
if (shipmentId == null) throw new IllegalArgumentException("Missing shipment ID");
shipmentId = shipmentId.strip();
if (shipmentId.isBlank() || grams < 0 || grams > 47_000)
throw new IllegalArgumentException("Invalid shipment mass");
}
}
public static ShipmentMass accept(String id, int grams) {
return new ShipmentMass(id, grams);
}
}Performance and ownership cost
strip scans the identifier in O(L) time and may allocate a new string; the numeric checks are O(1). The record stores one reference and one integer. The constructor does not copy a mutable object because these components are a String and a primitive.
Common Mistakes
- Do not assign directly to a record component field in a compact constructor.
- Do not call a record deeply immutable when it contains mutable components.
- Do not scatter validation only across selected factory methods.
Connected lessons
- Java records: value carriers and defensive copies
- Java records with arrays: copy on input and output
- Java identifier input: explicit alphabets and normalized display text
- Java interface defaults: resolve a two-parent method conflict
- Java instanceof patterns: keep a narrowed value inside its valid scope
- Java switch expressions: return one value from every state
- Java sealed hierarchies: switch over permitted request kinds
- Java generics and language contracts quiz
- Advanced Java
