What is Prototype pattern
The Prototype pattern is a Creational design pattern that enables the creation of new objects by cloning an existing instance (the prototype) rather than constructing them from...
🟢 Junior Level
The Prototype pattern is a Creational design pattern that enables the creation of new objects by cloning an existing instance (the prototype) rather than constructing them from scratch via the new operator.
Real-World Analogy
Think of a photocopier or a blueprint template. If you need to generate a new legal agreement with dozens of standard clauses, boilerplate configurations, and formatting rules, it is vastly faster to copy a pre-configured template and modify only the client-specific fields (e.g., name and address) than to retype the entire document from a blank page.
Idiomatic Java Implementation: Copy Constructor
In Java, the industry standard and safest mechanism for implementing the Prototype pattern is the Copy Constructor paired with a static factory method:
public class UserProfile {
private String username;
private String role;
private List<String> permissions;
// Standard constructor
public UserProfile(String username, String role, List<String> permissions) {
this.username = username;
this.role = role;
this.permissions = new ArrayList<>(permissions);
}
// Copy Constructor (Prototype mechanism)
public UserProfile(UserProfile prototype) {
this.username = prototype.username;
this.role = prototype.role;
// Defensive copy to prevent shared mutable state
this.permissions = new ArrayList<>(prototype.permissions);
}
public static UserProfile copyOf(UserProfile prototype) {
return new UserProfile(prototype);
}
public void addPermission(String permission) {
this.permissions.add(permission);
}
}
// Client usage:
UserProfile adminTemplate = new UserProfile("admin_base", "ADMIN", List.of("READ", "WRITE"));
UserProfile newAdmin = UserProfile.copyOf(adminTemplate);
newAdmin.addPermission("DELETE");
When to Use Prototype
- Expensive Instantiation: Object creation involves heavy disk I/O, database queries, remote RPC calls, or intense mathematical computations.
- Configuration Templates: The system requires numerous similar objects that share 90% of their state and differ only in minor details.
- Decoupling from Concrete Types: The client needs to duplicate objects polymorphically without knowing their exact runtime classes.
🟡 Middle Level
Why java.lang.Cloneable and Object.clone() Are Flawed
Java includes a built-in cloning mechanism via the Cloneable interface and Object.clone(). However, it is widely recognized by JVM architects and Joshua Bloch (Effective Java) as a severely flawed design:
// ❌ PROBLEMATIC LEGACY JAVA APPROACH
public class BrokenUser implements Cloneable {
private String name;
private List<String> roles;
@Override
public BrokenUser clone() {
try {
return (BrokenUser) super.clone(); // ⚠️ Shallow copy by default!
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
Fatal Defects of Cloneable:
- Marker Interface Without Methods:
Cloneabledoes not declare a publicclone()method. The method is defined onjava.lang.Objectasprotected native. ImplementingCloneabledoes not enforce a compile-time contract for callers. - Shallow Copy by Default:
super.clone()copies memory bitwise. Object references (collections, sub-objects) point to the identical heap locations:BrokenUser copy = original.clone(); copy.getRoles().add("ROOT"); // ❌ Mutates the original object's list! - Incompatible with
finalFields:clone()cannot reassign values tofinalfields becausefinalfields may only be initialized inside constructors. Deep cloning of immutable objects usingclone()is impossible without reflection hacks. - Bypasses Constructors:
super.clone()allocates the object via direct native JVM memory replication, completely bypassing class constructors and their invariant validation logic. - Checked Exception Overhead: Forces callers to catch or rethrow archaic
CloneNotSupportedException.
Shallow Copy vs Deep Copy
Shallow Copy:
┌─────────────────┐
│ UserProfile │
│ username: "Ann"│
│ permissions ───┼────────┐
└─────────────────┘ │
▼
Shallow Clone: ┌───────────────┐
┌─────────────────┐ │ ArrayList │ (Both instances point to
│ UserProfile │ │ ["READ"] │ the EXACT SAME heap list!)
│ username: "Bob"│ └───────────────┘
│ permissions ───┼────────▲
└─────────────────┘ │
Deep Copy:
┌─────────────────┐ ┌───────────────┐
│ UserProfile (1) │ ──────> │ ArrayList (1) │
└─────────────────┘ └───────────────┘
┌─────────────────┐ ┌───────────────┐
│ UserProfile (2) │ ──────> │ ArrayList (2) │ (Fully independent copies)
└─────────────────┘ └───────────────┘
Deep Cloning Strategies:
- Copy Constructors: The fastest, type-safe, compile-time verified approach.
- JSON Serialization (Jackson / Fastjson):
public static <T> T deepCopyJson(T object, Class<T> clazz, ObjectMapper mapper) { try { byte[] jsonBytes = mapper.writeValueAsBytes(object); return mapper.readValue(jsonBytes, clazz); } catch (IOException e) { throw new UncheckedIOException(e); } } - Java Native Serialization: Requires
Serializable, has severe performance overhead, and poses deserialization security vulnerabilities.
Prototype Scope in Spring Framework (@Scope("prototype"))
In Spring Boot, beans configured with prototype scope return a new instance on every container request:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ReportGenerator {
private final UUID reportId = UUID.randomUUID();
}
⚠️ The Prototype-in-Singleton Injection Pitfall
If you inject a prototype bean into a standard singleton bean using @Autowired, Spring injects it only once during application bootstrap:
@Service // Singleton
public class OrderService {
@Autowired
private ReportGenerator generator; // ❌ BUG: Same instance reused for all orders!
}
✅ Idiomatic Solutions:
ObjectProvider<T>(Recommended in Modern Spring):@Service public class OrderService { private final ObjectProvider<ReportGenerator> generatorProvider; public OrderService(ObjectProvider<ReportGenerator> generatorProvider) { this.generatorProvider = generatorProvider; } public void processOrder() { ReportGenerator gen = generatorProvider.getObject(); // Fresh prototype instance! } }@LookupMethod Injection: Spring uses CGLIB bytecode generation to intercept the method call and fetch a new instance fromApplicationContext.
🔴 Senior Level
HotSpot JVM Internals of Object.clone()
In OpenJDK HotSpot, Object.clone() is implemented in C++ (JVM_Clone in jvm.cpp):
- The JVM verifies whether the object’s class implements
Cloneable. If not, it throwsCloneNotSupportedException. - The JVM allocates a contiguous memory segment in Eden/Tenured heap space matching the exact object size.
- The JVM performs a direct memory copy (
memcpy/ raw memory block transfer) of the object’s header (Mark Word + Klass Word) and instance data fields. - No Java constructor (
<init>) is executed. Any auditing, security checks, UUID generations, or listener registrations present in constructors are completely skipped.
Spring Prototype Lifecycle Gotcha: The Missing @PreDestroy
There is a fundamental difference in lifecycle management between Spring Singleton and Prototype beans:
- Initialization: Spring fully manages prototype creation—it injects dependencies, runs
@PostConstruct, invokesInitializingBean.afterPropertiesSet(), and appliesBeanPostProcessors. - Destruction: Spring does NOT manage the destruction lifecycle of prototype beans.
- The container hands the prototype instance to the caller and completely forgets about it.
@PreDestroyandDisposableBean.destroy()are NEVER invoked by Spring for prototype beans.- If a prototype bean acquires open file handles, database connections, or socket channels, the caller must manually clean them up, or resource leaks will occur.
Cloning Mechanism Performance Benchmarks
| Cloning Mechanism | Latency Overhead | Constructor Invoked | Supports final Fields |
Safety & Robustness |
|---|---|---|---|---|
| Copy Constructor | 🟢 1x (~15–20 ns) | ✅ Yes | ✅ Yes | ✅ Compile-time safe |
Object.clone() |
🟡 ~1.5x (~25–30 ns) | ❌ No (raw memory copy) | ❌ Broken for deep copy | ⚠️ Shallow, requires casts |
| Jackson JSON Serialization | 🔴 ~25x–30x (~500 ns) | ✅ Yes | ⚠️ Requires getters/setters | ⚠️ Runtime schema overhead |
| Java Native Serialization | 🔴 ~50x–60x (~1000+ ns) | ❌ No | ⚠️ Complex setup | ❌ Security vulnerabilities |
4 Tricky Questions
1. Why does super.clone() break encapsulation and class invariants in classes with final fields?
Answer: super.clone() allocates heap memory and performs a bitwise shallow copy of fields at the JVM level without invoking any constructor. If a class contains a final reference to a mutable object (e.g., private final List<String> items), the cloned object’s final field will point to the exact same list instance in the heap. Because Java forbids reassigning final fields outside of constructors, the cloned object cannot replace that list reference with an independent deep copy. Thus, any mutation performed on the clone corrupts the original object’s state, breaking class encapsulation and immutability invariants.
2. Does Spring invoke @PreDestroy on beans configured with @Scope("prototype")? How should resource cleanup be managed?
Answer: No. Spring explicitly delegates prototype bean destruction to the client application. While Spring executes all initialization callbacks (@PostConstruct, BeanPostProcessor), it discards references to prototype beans once handed to the caller. Consequently, @PreDestroy and DisposableBean.destroy() are never invoked when the application context closes. If a prototype bean manages unmanaged system resources (such as TCP sockets or database connections), the calling code must register a custom destruction callback, implement AutoCloseable, or use a custom BeanPostProcessor to clean up resources.
3. How can you inject a prototype-scoped bean into a singleton-scoped bean so that a new instance is created on each request?
Answer: Injecting a prototype bean via standard @Autowired binds it only once when the singleton is created. To obtain a fresh instance on each invocation, you can use:
ObjectProvider<T>.getObject(): Modern and idiomatic in Spring 5+; injects a lazy provider without directly exposingApplicationContext.@LookupMethod Injection: Spring uses CGLIB at runtime to generate a dynamic subclass overriding an abstract or concrete method to query theApplicationContexton every call.Provider<T>(JSR-330) orApplicationContext.getBean(): Direct programmatic lookups.
4. What happens under the hood in the HotSpot JVM when Object.clone() is executed, and why are constructor side effects skipped?
Answer: In HotSpot, Object.clone() is a native C++ call (JVM_Clone). If the target class implements Cloneable, the JVM calculates the exact object size from the Klass pointer and allocates a new memory block in heap Eden space. It then executes a raw memory block transfer (similar to C memcpy), replicating the Mark Word, Klass pointer, and all primitive/reference fields directly. Because this memory copying operates below the Java bytecode interpreter and JIT level, bytecode instructions like invokespecial <init> are never executed. Therefore, constructor side effects—such as ID generators, metrics incrementers, or security checks—are completely bypassed.
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
“The Prototype pattern enables the creation of new objects by cloning a pre-existing template instance, which is ideal when direct construction is computationally expensive or requires loading complex configurations. In Java, standard
CloneableandObject.clone()are widely regarded as flawed language designs because they bypass constructors, perform shallow bitwise copies, and breakfinalfields. The production standard is using Copy Constructors or static copy factory methods (copyOf). In Spring, the pattern is represented by@Scope("prototype"), but developers must remember that Spring does not call@PreDestroyon prototype beans and that injecting them into singletons requiresObjectProvider<T>or@Lookup.”
Key Takeaways
- Prototype Intent: Copy existing pre-configured objects rather than building from scratch.
- Copy Constructor >
Cloneable: AvoidCloneable; usenew MyClass(prototype)with defensive copying. - Shallow vs Deep: Shallow copies duplicate memory pointers; deep copies recursively clone nested data structures.
- Spring Prototype Gotchas: Never rely on
@PreDestroyfor prototypes; useObjectProvider<T>to inject prototypes into singletons. - HotSpot Mechanics:
Object.clone()performs native memory copy without running class constructors.
Red Flags & Anti-Patterns
- 🚩 Implementing
Cloneablein new Java code: Indicates unawareness of Java language defects. - 🚩 Assuming
@Autowired PrototypeBeangenerates new instances per method call inside a Singleton. - 🚩 Assuming Spring cleans up Prototype beans via
@PreDestroy: Leads to memory and file descriptor leaks. - 🚩 Performing shallow copies of mutable collections: Leads to shared state and concurrent data race corruption.
Related Topics
- When to use Builder — Step-by-step object assembly
- Difference between Factory Method and Abstract Factory — Creational factories
- What is Singleton — Singleton vs Prototype scopes
- What pattern categories exist — Creational pattern taxonomy
- What anti-patterns do you know — Common anti-patterns and pitfalls