Java Object Construction
Purpose
Decide how instances are obtained, and keep that decision reversible. Three failure modes
this exists to prevent: the overload set where callers pick the wrong constructor because
the types happen to match; the constructor that does real work — I/O, registration,
overridable calls — so the object is unusable in a test and observable half-built; and the
singleton or static field that is treated as global state when its actual scope is one
class loader in one JVM among N replicas.
Workflow
Use Java 21 without preview as the example baseline. Inspect compiler/toolchain, runtime,
framework construction/serialization rules and supported callers before changing a creation
path. Do not upgrade Java or add a container for these examples. Reference code blocks are
partial or alternative sketches; supply imports and collaborators, and compile alternatives
separately. Missing lifecycle/identity evidence makes a recommendation conditional.
- State what the caller needs to say. If two ways of creating the object differ in
meaning rather than in parameter types, that difference belongs in a name, not in an
overload.
Money.ofMinor(1050) and Money.ofMajor(new BigDecimal("10.50")) should not be
two ambiguous constructors—and an exact decimal must not pass through a double.
- Pick the cheapest form that carries it. Canonical/compact constructor of a record →
named static factory → static factory plus private constructor → builder. Stop at the
first that fits;
java-fluent-apis owns the builder threshold.
- Decide instance control explicitly. Does every successful call have to produce a fresh
identity? A factory may cache, canonicalise, share or allocate. Identity becomes a contract
only if the API promises it; otherwise callers must use value equality. Bound any cache.
- Keep ordinary domain constructors side-effect-contained. Validate, normalize, assign.
Resource-owning types may necessarily acquire a resource and must define failure/cleanup
semantics. Never register/start threads/call overridable methods/let
this escape during
construction—see java-immutability's safe-publication rules.
- Push variability to the caller. Anything the class cannot substitute later — clock,
HTTP client, repository, random source — arrives through the constructor.
new on such
a thing inside domain logic is the decision you will need to undo first.
- Verify. Domain construction is testable without unrelated infrastructure. Resource-owner
integration tests cover acquisition failure and cleanup using isolated resources. Any static
mutable state has a justified scope, concurrency and shutdown policy; each factory's identity
promise (fresh, cached, or unspecified) is written down.
Rules
- Prefer a named static factory when the class has more than one meaningful way to be
created, when creation may return a cached or a substituted instance, or when the return
type should be an interface or sealed supertype rather than the concrete class. Prefer a
public constructor when there is exactly one way, it always allocates, and the type is
the type.
- Follow the platform naming conventions —
of, from, valueOf, instance/getInstance,
create/newInstance, copyOf, parse. A factory called build, make or get on a
type whose neighbours use of costs the caller a Javadoc lookup.
- A private-constructor-only surface blocks ordinary external subclass construction; nested
code with private access is a separate case. Use
final when the type must prohibit all
subclasses. Blocking external extension is usually the
point; take it deliberately, not by accident, and say so in the Javadoc rather than
leaving callers to discover it from a compile error.
- Document whether fresh or canonical identity is guaranteed. An implementation is free to add
or remove an undocumented cache while preserving value semantics; callers using
== on that
basis are wrong. A documented freshness/canonicalization guarantee is an API commitment and
constrains future implementations.
- Never cache without a bound. An unbounded interning map keyed by user or tenant data is a
leak with a factory in front of it — see java-reference-types-and-leaks.
- Do not synchronise on, or key identity off, a value-based class (
Optional, LocalDate,
Integer, the boxed primitives). Their identity is explicitly unspecified and the
identity-sensitive operations are documented as subject to failure in a future release.
- Enforce noninstantiability with a private constructor that throws, not with
abstract:
an abstract class is still instantiable through a subclass, and reads as "extend me".
- Within standard reflection and Java serialization, a single-element enum has the strongest
built-in singleton guarantees. A
private static final field plus private constructor can be
bypassed by deep reflection (subject to module/access policy) and serialization creates another
instance unless readResolve returns the canonical one. Fields need not all be transient for
identity, though serializing instance state may be wasteful or unsafe.
- A singleton's scope is one class loader in one JVM. It is not a global lock, not a
cluster-wide counter and not a distributed cache. When uniqueness must hold across
replicas, that is leader-election or distributed-locks-and-leases, and the local
singleton is at best a per-process handle to it.
- Static mutable state makes tests order-dependent and makes horizontal scaling change
behaviour. If it must exist, it belongs in an injected, replaceable object whose lifetime
the composition root controls — a container-managed singleton bean is that, a
static
field is not.
- Prefer the lazy-initialisation holder class to double-checked locking when a static
really must be built lazily; and prefer eager initialisation to both unless the cost of
building it is proven and the object is genuinely often unused. java-memory-model owns
the correctness argument.
For the change report, name the creation/identity contract, preserved callers, ownership on
success/failure, and targeted checks run. Tests of one implementation do not establish a new
public identity guarantee or a cluster-wide singleton.
References
- Factories and instance control — read
when choosing between a constructor, a named factory and a record's canonical
constructor, when naming a factory, when a factory will cache or canonicalise
instances, or when a factory's return type must survive API evolution.
- Singletons and static state — read whenever
a singleton, a static registry, a static cache or a static mutable field is proposed or
found: the forms, what each actually defends against, the testing and class-loader
consequences, and what changes when the process is one of many.
1---2name: java-object-construction3description: Choosing how an object comes into existence in Java: static factory versus public constructor, the of/from/valueOf/getInstance naming conventions, instance control (caching, canonicalisation, value-based classes), enum and holder singletons, noninstantiable utility classes, and passing collaborators in rather than hardwiring them with new. Use when a class has several constructors distinguished only by parameter types, when a constructor does work beyond assigning fields, when a singleton or a static mutable field is proposed, when new appears inside domain logic for something the code can never substitute in a test, or when a factory hands back a type its callers should not be able to name. Does not cover builders and fluent chains (java-fluent-apis), which dependency edge should exist at all (java-dependency-inversion), defensive copying of components (java-immutability), or releasing what construction acquires (java-resource-management).4---56# Java Object Construction78## Purpose910Decide how instances are obtained, and keep that decision reversible. Three failure modes11this exists to prevent: the overload set where callers pick the wrong constructor because12the types happen to match; the constructor that does real work — I/O, registration,13overridable calls — so the object is unusable in a test and observable half-built; and the14singleton or static field that is treated as global state when its actual scope is one15class loader in one JVM among N replicas.1617## Workflow1819Use Java 21 without preview as the example baseline. Inspect compiler/toolchain, runtime,20framework construction/serialization rules and supported callers before changing a creation21path. Do not upgrade Java or add a container for these examples. Reference code blocks are22partial or alternative sketches; supply imports and collaborators, and compile alternatives23separately. Missing lifecycle/identity evidence makes a recommendation conditional.24251. **State what the caller needs to say.** If two ways of creating the object differ in26 _meaning_ rather than in parameter types, that difference belongs in a name, not in an27 overload. `Money.ofMinor(1050)` and `Money.ofMajor(new BigDecimal("10.50"))` should not be28 two ambiguous constructors—and an exact decimal must not pass through a `double`.292. **Pick the cheapest form that carries it.** Canonical/compact constructor of a record →30 named static factory → static factory plus private constructor → builder. Stop at the31 first that fits; `java-fluent-apis` owns the builder threshold.323. **Decide instance control explicitly.** Does every successful call have to produce a fresh33 identity? A factory may cache, canonicalise, share or allocate. Identity becomes a contract34 only if the API promises it; otherwise callers must use value equality. Bound any cache.354. **Keep ordinary domain constructors side-effect-contained.** Validate, normalize, assign.36 Resource-owning types may necessarily acquire a resource and must define failure/cleanup37 semantics. Never register/start threads/call overridable methods/let `this` escape during38 construction—see java-immutability's safe-publication rules.395. **Push variability to the caller.** Anything the class cannot substitute later — clock,40 HTTP client, repository, random source — arrives through the constructor. `new` on such41 a thing inside domain logic is the decision you will need to undo first.426. **Verify.** Domain construction is testable without unrelated infrastructure. Resource-owner43 integration tests cover acquisition failure and cleanup using isolated resources. Any static44 mutable state has a justified scope, concurrency and shutdown policy; each factory's identity45 promise (fresh, cached, or unspecified) is written down.4647## Rules4849- Prefer a named static factory when the class has more than one meaningful way to be50 created, when creation may return a cached or a substituted instance, or when the return51 type should be an interface or sealed supertype rather than the concrete class. Prefer a52 public constructor when there is exactly one way, it always allocates, and the type is53 the type.54- Follow the platform naming conventions — `of`, `from`, `valueOf`, `instance`/`getInstance`,55 `create`/`newInstance`, `copyOf`, `parse`. A factory called `build`, `make` or `get` on a56 type whose neighbours use `of` costs the caller a Javadoc lookup.57- A private-constructor-only surface blocks ordinary external subclass construction; nested58 code with private access is a separate case. Use `final` when the type must prohibit all59 subclasses. Blocking external extension is usually the60 point; take it deliberately, not by accident, and say so in the Javadoc rather than61 leaving callers to discover it from a compile error.62- Document whether fresh or canonical identity is guaranteed. An implementation is free to add63 or remove an undocumented cache while preserving value semantics; callers using `==` on that64 basis are wrong. A documented freshness/canonicalization guarantee is an API commitment and65 constrains future implementations.66- Never cache without a bound. An unbounded interning map keyed by user or tenant data is a67 leak with a factory in front of it — see java-reference-types-and-leaks.68- Do not synchronise on, or key identity off, a value-based class (`Optional`, `LocalDate`,69 `Integer`, the boxed primitives). Their identity is explicitly unspecified and the70 identity-sensitive operations are documented as subject to failure in a future release.71- Enforce noninstantiability with a private constructor that throws, not with `abstract`:72 an abstract class is still instantiable through a subclass, and reads as "extend me".73- Within standard reflection and Java serialization, a single-element enum has the strongest74 built-in singleton guarantees. A `private static final` field plus private constructor can be75 bypassed by deep reflection (subject to module/access policy) and serialization creates another76 instance unless `readResolve` returns the canonical one. Fields need not all be transient for77 identity, though serializing instance state may be wasteful or unsafe.78- A singleton's scope is one class loader in one JVM. It is not a global lock, not a79 cluster-wide counter and not a distributed cache. When uniqueness must hold across80 replicas, that is leader-election or distributed-locks-and-leases, and the local81 singleton is at best a per-process handle to it.82- Static mutable state makes tests order-dependent and makes horizontal scaling change83 behaviour. If it must exist, it belongs in an injected, replaceable object whose lifetime84 the composition root controls — a container-managed singleton bean is that, a `static`85 field is not.86- Prefer the lazy-initialisation holder class to double-checked locking when a static87 really must be built lazily; and prefer eager initialisation to both unless the cost of88 building it is proven and the object is genuinely often unused. java-memory-model owns89 the correctness argument.9091For the change report, name the creation/identity contract, preserved callers, ownership on92success/failure, and targeted checks run. Tests of one implementation do not establish a new93public identity guarantee or a cluster-wide singleton.9495## References9697- [Factories and instance control](references/factories-and-instance-control.md) — read98 when choosing between a constructor, a named factory and a record's canonical99 constructor, when naming a factory, when a factory will cache or canonicalise100 instances, or when a factory's return type must survive API evolution.101- [Singletons and static state](references/singletons-and-static-state.md) — read whenever102 a singleton, a static registry, a static cache or a static mutable field is proposed or103 found: the forms, what each actually defends against, the testing and class-loader104 consequences, and what changes when the process is one of many.