Choose the pattern that matches the object’s shape and lifecycle. Builders fit complex construction with many optional fields. Withers fit small, incremental updates to an existing immutable instance. Records are the cleanest choice for simple data carriers on Java 16 and above because they remove boilerplate and enforce immutability by default.
Choosing the Right Construction Pattern for the Job
Builders, withers, and records solve different problems, so the right choice depends on how the object is created and how often it changes after creation. For Java teams, the key question is not which option is “most modern,” but which one preserves immutability without making the code awkward, misleading, or hard to maintain.
Builders are strongest when the object has many optional fields, validation rules, or cross-field dependencies that would make a constructor unreadable. They also help when the creation process itself is a domain decision, such as assembling a configuration object from multiple sources. Withers fit a different need, where an object is already valid and you want to derive a new instance with one or two changes. Records suit simple data carriers whose shape is stable and whose main job is to transport values cleanly.
The practical mistake is forcing one pattern everywhere. In practice, teams usually create design debt when they use builders for trivial objects or records for objects that really need guarded construction.
How It Works in Practice
A builder is usually the best fit when construction has state before the object exists. That includes required fields, optional defaults, validation logic, normalization, and combinations that must be checked together. The builder keeps the object immutable once built, while making the creation call readable and explicit.
A wither is different because it does not construct from scratch. It creates a new instance from an existing immutable object, changing only the selected field. That makes it useful in workflows where an object lives for a while and then needs a small revision, such as updating a status, timestamp, or derived property without exposing mutation.
Records are the most compact option when the object is fundamentally a value tuple with stable fields and little or no custom construction logic. They reduce boilerplate, give you final fields, and make the intent obvious. They work best when the type exists to hold data rather than to protect a complicated invariant.
- Use a builder when readability improves as construction gets more complex.
- Use a wither when the domain model is immutable but derived variations are common.
- Use a record when the type is a straightforward value carrier and the canonical constructor is enough.
If the object needs heavy validation, hidden defaults, or multiple legal construction paths, records alone tend to break down because the construction rules become awkward to express cleanly.
Common Variations and Edge Cases
Tighter immutability often increases construction friction, so teams need to balance simplicity for callers against protection of object invariants. That tradeoff shows up most clearly when a class starts as a simple data holder and gradually accumulates domain rules.
One common edge case is a record that later needs behavior. Records can still have methods, but once construction starts requiring complex validation or multiple alternate inputs, a record may stop being the cleanest model. Another edge case is a builder used only to avoid a long constructor, which adds indirection without real value. In that case, the builder pattern is solving a readability problem that the design itself created.
Withers also deserve restraint. They are useful when changes are small and local, but they can become noisy if an object is frequently updated in many steps. At that point, the team should question whether the object is really a value object or whether the workflow is trying to model mutable state through repeated copying.
Current guidance suggests treating records as the default for plain immutable values, builders as the default for complex creation, and withers as a targeted tool for controlled derivation. The best choice is the one that keeps the domain model honest, not the one that looks elegant in isolation.
Standards & Framework Alignment
This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.
CIS Controls v8 and NIST CSF 2.0 set the governance and control requirements practitioners need to meet.
| Framework | Control / Reference | Relevance |
|---|---|---|
| CIS Controls v8 | CIS Control 4 — Secure Configuration of Enterprise Assets and Software | Immutable value-object design supports controlled software behavior and reduces risky ad hoc state handling. |
| Recommendation — Use secure coding and configuration review to standardize immutable object construction patterns. | ||
| NIST CSF 2.0 | PR.DS — Data Security | Immutable value objects help preserve data integrity by preventing unintended state changes. |
| Recommendation — Design value objects to preserve integrity and reduce accidental mutation. | ||
Practitioner Guidance
Decision rule: If construction rules are the hard part, choose a builder. If post-creation variation is the hard part, choose withers. If neither is hard and the type is just a value carrier, choose a record.
What to verify: Check whether the object has hidden invariants, alternate valid input paths, or defaults that callers should not assemble manually. If yes, a record may be too thin and a builder will usually communicate intent better. Also verify whether the type will likely grow optional fields soon, because that is often the point where constructor-based code starts to degrade.
Common mistake: Teams often pick records because they are concise, then bolt on enough validation and factory logic that the record no longer feels simple. When that happens, the design has outgrown the pattern, and the code becomes harder to read than a builder-based alternative.
Practitioner takeaway: Choose the pattern that matches the object’s lifecycle, not the one that is shortest to write today; immutable design is successful when it keeps construction, derivation, and meaning equally clear.
Related resources from NHI Mgmt Group
- How should teams choose between session-based auth and JWT in Java applications?
- How should security teams choose between RBAC, ABAC, and PBAC for NHI access?
- How should security teams choose between basic, predefined, and custom GCP IAM roles?
- How should security teams choose between SAML and OIDC?
Deepen Your Knowledge
Reviewed and updated by the NHIMG editorial team on September 16, 2026.
NHI Mgmt Group — the #1 independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org