Prefer explicit Java copy strategies such as copy constructors and named factories when Cloneable would obscure the copy contract.
Many Java teams say “Prototype” when what they really need is “a safe way to build a new object from an existing one.” In that situation, clone() is only one option, and often not the best one.
Copy constructors and named copy factories make the copy contract visible.
1public final class ConnectionPolicy {
2 private final Duration timeout;
3 private final boolean retryEnabled;
4
5 public ConnectionPolicy(Duration timeout, boolean retryEnabled) {
6 this.timeout = timeout;
7 this.retryEnabled = retryEnabled;
8 }
9
10 public ConnectionPolicy(ConnectionPolicy source) {
11 this(source.timeout, source.retryEnabled);
12 }
13
14 public static ConnectionPolicy from(ConnectionPolicy source) {
15 return new ConnectionPolicy(source);
16 }
17}
A reader can see exactly how copying works. There is no hidden superclass behavior and no marker-interface convention to remember.
Best when the type owns its own copy policy and the fields are reasonably understandable.
Strengths:
Trade-off:
Useful when the copied object should be named by intent.
1public static PurchaseOrder draftFrom(PurchaseOrder source) {
2 return new PurchaseOrder(
3 null,
4 source.customer(),
5 List.copyOf(source.items()),
6 Status.DRAFT
7 );
8}
This is stronger than a generic clone when the copy intentionally changes fields such as status, ID, or timestamps.
Useful when a caller wants to start from an existing object and change several options before producing the result.
1Order revised = OrderBuilder.from(existingOrder)
2 .shippingAddress(newAddress)
3 .priority(Priority.EXPRESS)
4 .build();
This is usually better than many copyWith... methods when the customization surface is wide.
clone() And CloneableStill useful in some legacy or framework-oriented code, but no longer the best default recommendation for most Java application types.
Use it only when:
For records and other immutable data carriers, a “copy” is often just a new instance built from existing components:
1public record UserPreferences(String theme, Locale locale, boolean compactMode) {}
2
3UserPreferences compact =
4 new UserPreferences(existing.theme(), existing.locale(), true);
This is simpler than pretending immutability needs a special cloning protocol.
Avoid copy mechanisms that depend on:
Those approaches usually make copying less explicit right when the design needs more clarity.
Choose the option that makes these questions easiest to answer:
If a technique hides those answers, it is probably the wrong one.
When reviewing Java copy alternatives, ask:
Prototype does not require clone(). It requires a clear, repeatable way to create a new object from an existing one.