Skip to content
Back to skills

Gof Builder

ASecurity

Builder in modern Java: distinguish the original GoF separation of construction process from representation from the Effective Java fluent value builder. Covers selection signals rather than parameter-count thresholds, staged builders, invariant placement, mutable-builder concurrency hazards, Lombok/JPA boundaries, performance evidence, and test data builders. Use for ambiguous or telescoping construction, incremental input, multiple representations, or a builder that permits invalid combinat...

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 19, 2026
developmentgojavaexpresstestinggitapiperformance

Works with

  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned September 29, 2026

npx -y skills add robsonkades/agent-skills --skill gof-builder --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Gof Builder?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Gof Builder
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/robsonkades-gof-builder/badge)](https://www.skillsdirectory.com/skills/robsonkades-gof-builder)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: gof-builder
description: >
  Builder in modern Java: distinguish the original GoF separation of construction process from
  representation from the Effective Java fluent value builder. Covers selection signals rather
  than parameter-count thresholds, staged builders, invariant placement, mutable-builder
  concurrency hazards, Lombok/JPA boundaries, performance evidence, and test data builders. Use
  for ambiguous or telescoping construction, incremental input, multiple representations, or a
  builder that permits invalid combinations. Does not cover product-type selection
  (gof-factory-method, gof-abstract-factory), copying (gof-prototype), fluent APIs generally
  (java-fluent-apis), or value semantics (java-immutability).
---

# Builder

## Purpose

Make the construction of a complex object readable and safe. Java has no named arguments and no
default parameter values; a builder can simulate both and gives one place where the whole
object's invariants can be checked before publication.

The original GoF pattern also separates a **construction process** from the representations it
can produce: the same parser or director can drive a tree builder, text builder, or test builder.
That is distinct from the now-common Effective Java fluent builder for one value type. Name which
variant is intended; their selection criteria and failure modes differ.

For a fluent value builder, readability, defaults, staged input and invariant enforcement are the
usual justification. For a GoF representation builder, the justification is reuse of the
construction process across outputs. A builder that provides neither is ceremony.

Examples use Java 17 records/sealed types, without preview features. Inspect the target release,
generated code and framework versions first; this skill does not authorize a toolchain upgrade.

Start with consumer code: an ordinary creation, a relevant advanced use such as incremental input
or coordinated changes, and likely misuse such as missing values, conflicting choices or builder
reuse. Compare those calls using the simplest viable constructor/factory and the relevant builder
variant. Inspect existing callers and defaults before asking about missing semantics or ownership;
ask only when the answer could change the choice.

## When it is the answer

```text
A constructor whose arguments are easy to misread or mis-order
        → consider a builder, named factories, or stronger parameter types.

Several parameters are genuinely optional with sensible defaults
        → telescoping constructors otherwise, or nulls as "absent".

An invariant spans several fields and can only be checked when
all are known ("either accountId or iban, not both")
        → one validation point, before the object exists.

A collection is accumulated by the caller over several steps
        → addItem(...) reads better than assembling a list first.

The object is built from parsed or streamed input arriving in pieces
        → compare an incremental builder with local accumulation followed by a factory;
          input arrival alone does not require a public builder API.
```

## When it is not

- **A small, obvious value with required, well-typed components.** A record's canonical
  constructor already checks arity and types; Java remains positional, so repeated or weak types
  can still justify named factories or a builder.
- **All required components have unambiguous types.** A constructor often suffices; a conventional
  optional-setter builder can lose presence checks, though staged or required-argument builders need not.
- **The variants are few and nameable.** Two or three static factories
  (`Money.of`, `Money.zero`, `Retry.none`) beat a builder and document intent better.
- **A mutable builder is shared across requests without ownership.** Fix its lifecycle; storing
  a confined builder in a field or deliberately reusing it sequentially is not inherently unsafe.
- **A mutable bean has no construction constraints.** A value builder may add little. GoF builders
  can legitimately produce mutable trees/documents; define ownership and valid completion instead.

## Modern Java expression

```text
Small, all required                 record Point(int x, int y)
Few named shapes                    static factories on the record
Optional components, sensible
  defaults                          record + builder or named factories;
                                    framework config defaults are binding-specific
Deriving a variant of an instance   withX() for a small change; toBuilder()
                                    can support several coordinated changes
Required-then-optional, enforced
  at compile time                   staged builder (one interface per step)
Test fixtures                       test data builder with a valid default
```

A record plus a builder is not redundancy: the record owns the invariants and the identity, the
builder owns the ergonomics. Put validation in the **record's compact constructor**, not only in
`build()` — otherwise every other construction path, including deserialisation and `withX`
copies, bypasses it.

## Decision rules

```text
IF the type is small and the positional call remains unambiguous
THEN prefer a record or constructor; do not use parameter count alone as the decision.

IF several components are optional
THEN builder, or a record whose optional components have documented
     defaults supplied by named static factories.

IF some components are required and mis-ordering is possible
THEN either distinct types (a Money, an OrderId — not two Strings), or a
     staged builder or required-argument builder. Stages enforce calls, not non-null
     or semantically valid values; constructor validation still applies.

IF an intrinsic value invariant lives only in build() and other construction paths exist
THEN enforce it at the value's constructor/factory boundary and delegate from build().
     A GoF mutable representation may instead need explicit completion validation.

IF one construction process must produce different representations
THEN compare GoF builders with one intermediate model plus renderers. Retained inspection
     or editing favors a model; incompatible representations or a retention limit may
     favor direct builders. Define valid completion, failure cleanup, product ownership
     and any externally visible partial output; do not assume a success callback runs on failure.

IF the builder is stored in a field or shared between requests
THEN inspect ownership and escape paths. Prefer per-construction confinement;
     alternatives need an explicit ownership-transfer, immutable or synchronized lifecycle
     contract. Synchronizing individual setters/build() does not isolate the whole
     construction session; protect that session and define reuse/reset behavior.

IF the builder can produce an object that later throws because a
combination was illegal
THEN reject the combination no later than construction, naming both fields;
     build() delegates intrinsic invariants to the product boundary. Earlier rejection
     may enforce an explicit builder protocol. "field X is required" when Y was set is not enough.

IF @Builder is applied to a JPA entity
THEN verify the generated constructor path, identity/lifecycle rules, association
     defaults and ORM constructor requirements. Prefer domain factories when they
     make valid aggregate creation clearer (orm-structural-mapping).
```

## Cross-cutting checks

- **Concurrency.** A conventional mutable builder is not thread-safe by default. Inspect actual
  aliases and overlapping use, not field placement or lambda capture alone. Prefer construction
  within one owner and publication of the finished immutable value; transferring a builder to
  another owner needs safe publication and exclusive use during that ownership.
- **Distribution.** Builders are the normal shape for protocol messages and outbound requests,
  and generated ones (protobuf, Avro, gRPC, cloud SDKs) already exist — reuse them unless an
  application-owned contract or validation boundary justifies an adapter. What crosses the wire is the built value, so its invariants must hold after
  deserialisation too: a builder-enforced rule that the deserialiser does not re-run is not
  enforced (`java-immutability`).
- **Performance.** A conventional mutable builder introduces a candidate allocation and may also
  allocate collection buffers or staged lambdas. HotSpot may scalar-replace a non-escaping
  builder, but this is compilation- and call-site-dependent. Measure allocation and retained
  data before changing construction (`jmh-microbenchmarks`, `allocation-profiling`).
- **Testing.** Test data builders are the strongest everyday use of this pattern: a builder with
  a valid default for every field, where a test names only what it cares about. It keeps tests
  readable when a required field is added, because only the builder changes
  (`java-test-design`).

## Review checklist

- [ ] Value-product constructors enforce invariants; mutable GoF products have explicit ownership and completion rules
- [ ] Every public value-construction path enforces intrinsic invariants; builder-specific state checks remain local
- [ ] Cross-field rules hold at the product boundary; constructor/build diagnostics identify conflicting fields
- [ ] Required components are enforced — by a staged builder, or by a check that names them
- [ ] Builder confinement, reuse/reset semantics and failed-build behavior are explicit
- [ ] Representation builders publish complete results only on success; streaming partial output
      and abort cleanup have explicit contracts independent of a success callback
- [ ] Call-site ambiguity, optionality, staged construction, or representation variance actually
      justifies it; parameter count alone does not
- [ ] Immutable products snapshot collections at construction; mutable elements are addressed separately
- [ ] Variant construction revalidates the result, whether through factories, `withX` or `toBuilder`
- [ ] Generated-builder adapters have a concrete boundary benefit rather than merely duplicating setters

Report the selected construction form, consumer evidence, invariant/ownership boundary and validation
performed. Keeping a constructor or factory is a valid outcome; justify builder machinery when it
adds value. For a small review, a concrete finding and focused check suffice.

## References

- [Decision and alternatives](references/decision-and-alternatives.md) — selection signals,
  records and static factories against builders, staged builders and
  what they cost, where validation must live, representation-builder completion and ownership,
  and the Lombok `@Builder` failure modes on entities and records. Read before adding or removing
  a builder; use the representation section for parser/director-driven construction.
- [Worked example](references/worked-example.md) — a payment instruction with mutually exclusive
  fields, taken from telescoping constructors to a record and builder, then a staged API sketch,
  with the validation placement made explicit and a test data
  builder derived from it. Read when implementing.

Files in this skill

  • SKILL.md9.8 KB
  • references/decision-and-alternatives.md9.5 KB
  • references/worked-example.md8.8 KB
  • skill.yaml1.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…