What is Prototype Pattern?
4. Doesn't work with final fields 5. Checked exception CloneNotSupportedException
Junior Level
Prototype is a pattern that allows creating new objects by copying an existing one. Copy constructor is the recommended Java alternative to Object.clone().
Simple analogy: Document photocopying. Instead of writing a document from scratch each time, you copy a ready-made template and change the needed fields.
Example:
// Copy constructor
public class User {
private String name;
private List<String> permissions;
// Ordinary constructor
public User(String name, List<String> permissions) {
this.name = name;
this.permissions = new ArrayList<>(permissions);
}
// Copy constructor (Prototype)
public User(User other) {
this.name = other.name;
this.permissions = new ArrayList<>(other.permissions); // Copy the list!
}
}
// Usage
User template = new User("admin", List.of("read", "write"));
User copy = new User(template); // Copy of the template!
When to use:
- Object creation is expensive (DB request, network)
- Many similar objects are needed
- Constructor is complex
Middle Level
Why Cloneable in Java is Bad
// Problems with Cloneable
public class User implements Cloneable {
private String name;
private List<String> permissions;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // Shallow copy!
}
}
User original = new User("Ivan", new ArrayList<>(List.of("read")));
User copy = (User) original.clone();
// Shallow copy:
copy.getPermissions().add("admin");
// original.getPermissions() also changed!
Cloneable problems:
- Doesn’t contain a
clone()method (marker interface) clone()is protected — needs overriding- Shallow copy by default
- Doesn’t work with
finalfields - Checked exception
CloneNotSupportedException
Solution: Copy Constructor
// Recommended approach (Joshua Bloch)
public class User {
private final String name;
private final List<String> permissions;
// Copy constructor
public User(User other) {
this.name = other.name;
this.permissions = new ArrayList<>(other.permissions); // Deep copy!
}
// Static factory method
public static User copyOf(User other) {
return new User(other);
}
}
User original = new User("Ivan", List.of("read"));
User copy = User.copyOf(original); // Clear and type-safe
Deep vs Shallow Copy
// Shallow — copies references
public User(User other) {
this.name = other.name; // String — immutable, OK
this.permissions = other.permissions; // Reference to the same list!
}
// Deep — copies everything
public User(User other) {
this.name = other.name;
this.permissions = new ArrayList<>(other.permissions); // New list
this.address = new Address(other.address); // Copy nested object
}
Prototype Scope in Spring
// Prototype = new instance on each request
@Component
@Scope("prototype")
public class OrderProcessor {
private final Order order;
public OrderProcessor(Order order) {
this.order = order; // New Order each time
}
}
// Usage
OrderProcessor p1 = context.getBean(OrderProcessor.class);
OrderProcessor p2 = context.getBean(OrderProcessor.class);
// p1 != p2 -> different objects!
// Trap: Prototype inside Singleton
@Component // Singleton!
public class OrderService {
@Autowired
private OrderProcessor processor; // Created ONCE!
}
// Solution 1: @Lookup
@Component
public abstract class OrderService {
@Lookup
protected abstract OrderProcessor getProcessor();
}
// Solution 2: ObjectProvider
@Component
public class OrderService {
private final ObjectProvider<OrderProcessor> processorProvider;
public void process() {
OrderProcessor processor = processorProvider.getObject(); // New each time!
}
}
Cloning via Serialization
// For complex objects with deep nesting
public static <T extends Serializable> T deepClone(T object) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(object);
try (ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais)) {
return (T) ois.readObject();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// Or via Jackson (JSON)
public static <T> T cloneViaJson(T object, Class<T> clazz) {
String json = objectMapper.writeValueAsString(object);
return objectMapper.readValue(json, clazz);
}
Typical Mistakes
- Shallow copy of mutable fields
// Changing the copy affects the original copy.getList().add("item"); original.getList().contains("item"); // true! - Prototype inside Singleton
// Prototype will be created once @Service public class MyService { @Autowired private PrototypeBean bean; // Single instance! }
Senior Level
Cloneable Problems at JMM Level
// super.clone() works at Object.clone() level
// -> Copies memory, bypassing constructors
// -> final fields are NOT initialized
// -> Class invariant is violated
public class ImmutableUser {
private final String name;
private final List<String> permissions;
public ImmutableUser(String name, List<String> permissions) {
this.name = name;
this.permissions = List.copyOf(permissions);
}
// clone() cannot initialize final fields!
// Would need to use reflection or unsafe
}
Copy Constructor vs Cloneable: Benchmark
1M copies:
Constructor copy: 50ms (direct call)
clone(): 80ms (native method, doesn't use reflection. But bypasses constructor, so class invariants may be violated)
JSON clone: 500ms (serialization)
Serialization: 800ms (Java serialization)
Spring Prototype Lifecycle
// Spring does NOT manage Prototype bean lifecycle!
@Component
@Scope("prototype")
public class ExpensiveResource {
@PostConstruct
public void init() {
System.out.println("Created"); // Will be called
}
@PreDestroy
public void cleanup() {
System.out.println("Destroyed"); // Will NOT be called!
}
}
// Solution: manually clean up
@Component
public class ResourceCleaner {
private final List<ExpensiveResource> resources = new ArrayList<>();
public ExpensiveResource getResource() {
ExpensiveResource r = context.getBean(ExpensiveResource.class);
resources.add(r);
return r;
}
@PreDestroy
public void cleanup() {
resources.forEach(ExpensiveResource::cleanup);
}
}
Prototype for Optimization
// Cached prototype instead of expensive creation
public class DocumentTemplate {
private static final Document PROTOTYPE;
static {
// Expensive initialization (DB, network)
PROTOTYPE = loadFromDatabase();
}
public static Document createDocument() {
return new Document(PROTOTYPE); // Copy the prototype
}
}
Production Experience
Real scenario: Prototype + Singleton bug
- OrderProcessor (prototype) injected into OrderService (singleton)
- Bug: all orders processed by the same processor
- 4 hours of debugging -> found via thread dump
- Solution: ObjectProvider
Best Practices
- DON’T use Cloneable — copy constructor is better
- Deep copy for mutable fields
- @Scope(“prototype”) + ObjectProvider in Spring
- JSON cloning for complex objects
- Prototype cache for expensive objects
- @PreDestroy does NOT work for Prototype in Spring
- final fields are incompatible with clone()
Senior Summary
- Cloneable = defective design, avoid it
- Copy Constructor = type-safe, works with final
- Spring Prototype = new bean on each getBean()
- Lifecycle: @PreDestroy is NOT called for Prototype
- Singleton + Prototype = trap -> ObjectProvider/@Lookup
- Deep Clone: JSON/Serialization for complex objects
- Performance: constructor > clone() > JSON > serialization
Interview Cheat Sheet
Must know:
- Prototype — creating objects by copying existing ones, not via constructor
- Cloneable in Java — bad design: marker interface without clone() method, shallow copy, incompatible with final
- Recommended approach: copy constructor — type-safe, works with final fields
- Deep copy copies all mutable fields, shallow copy copies references (changes affect the original)
- Spring @Scope(“prototype”) — new instance on each getBean(), but @PreDestroy is NOT called
- Prototype in Singleton — trap: created once, solution via ObjectProvider or @Lookup
- Benchmark: constructor copy (50ms) > clone() (80ms) > JSON (500ms) > serialization (800ms)
Common follow-up questions:
- Why is Cloneable bad? — Marker interface, protected clone(), shallow copy by default, incompatible with final
- How does deep copying differ from shallow? — Deep copies all nested objects, shallow copies only references
- What happens if Prototype bean is injected into Singleton? — Created once during Singleton initialization
- How to deep copy a complex object? — Serialization (ByteArrayOutputStream) or JSON (Jackson)
Red flags (DO NOT say):
- “I use Cloneable — it’s the standard Java approach” — Joshua Bloch recommends Copy Constructor
- “Shallow copy is sufficient” — changing the copy affects the original for mutable fields
- “Prototype in Spring calls @PreDestroy” — NOT called for prototype beans
- “Cloneable is faster than copy constructor” — benchmark shows the opposite (80ms vs 50ms)
Related topics:
- [[08. When to use Builder]] — creational pattern
- [[07. Difference between Factory Method and Abstract Factory]] — creational patterns
- [[03. What is Singleton]] — Singleton vs Prototype scopes
- [[02. What pattern categories exist]] — Creational patterns
- [[16. What anti-patterns do you know]] — Copy-Paste Programming