The Prototype Pattern with Java Examples

I’m a backend software engineer with over a decade of experience primarily in Java. I started this blog to share what I’ve learned in a simplified, approachable way — and to add value for fellow developers. Though I’m an introvert, I’ve chosen to put myself out there to encourage more women to explore and thrive in tech. I believe that by sharing what we know, we learn twice as much — that’s precisely why I’m here.
What Is the Prototype Pattern?
A prototype is a pre-existing object that you use as a template to create new objects. Instead of creating an object from scratch, with newYou clone an existing one that already has the desired state.
The Prototype Pattern is a creational design pattern that lets you:
Create new objects by copying (not instantiating) a prototype
Avoid repeating complex setup logic
Keep code flexible by not tying it to specific class constructors
For example, here’s how you would copy an object without this pattern:
Book book1 = new Book("Effective Java", "Joshua Bloch", List.of("Item 1", "Item 2"));
Book book2 = new Book(
book1.getTitle(),
book1.getAuthor(),
new ArrayList<>(book1.getChapters())
);
You have to manually copy each field — error-prone and repetitive.
🧬 With Prototype Pattern (via copy method)
Book book1 = new Book("Effective Java", "Joshua Bloch", List.of("Item 1", "Item 2"));
Book book2 = book1.copy();
Much cleaner and encapsulated — the copy() method inside Book handles the duplication safely.
In Java, you have two alternatives to implement the pattern: implementing the Cloneable interface or defining a custom copy() method.
Ways to Implement the Prototype Pattern in Java
Option 1: Using Cloneable + super.clone()
🧩 Before We Start: What Is Cloneable and What Does It Do?
To use super.clone() in Java, your class must implement the Cloneable interface:
public interface Cloneable { }
It’s a marker interface — it has no methods. Its purpose is to signal the JVM that your object supports cloning. If your class does not implement Cloneable, calling super.clone() will throw a CloneNotSupportedException.
What You Must Do:
Implement
CloneableOverride
clone()and make itpublicHandle
CloneNotSupportedExceptionor throw itReturn the correct type by downcasting
Here’s the implementation of the Prototype pattern following this recipe:
public class Document implements Cloneable {
private String title;
private List<String> paragraphs;
public Document(String title, List<String> paragraphs) {
this.title = title;
this.paragraphs = paragraphs;
}
@Override
public Document clone() {
try {
return (Document) super.clone(); // Shallow copy
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // This should never happen
}
}
}
✅ Pros: fast, native shallow copy, you can skip the manual work of copying each field
❌ Cons: This is shallow copying, which might violate the principle of least surprise (more on that later)
Option 2: Custom Copy (No Cloneable)
You implement your copy method manually and have full control of what you copy and follow the copying approach (deep or shallow) that best follows your needs:
public class Document {
private String title;
private List<String> paragraphs;
public Document(String title, List<String> paragraphs) {
this.title = title;
this.paragraphs = paragraphs;
}
public Document copy() {
return new Document(title, new ArrayList<>(paragraphs));
}
}
✅ Pros: clearer, performs deep copy, no weird CloneNotSupportedException
❌ Cons: more manual work
📃 Shallow vs Deep Copy: Key Differences
Whether you're using Cloneable or a manual copy method, you can implement a deep or shallow copy of your objects. Understanding shallow vs deep copy is necessary:
Shallow Copy Example
Document original = new Document("My Title", new ArrayList<>(List.of("Intro", "Content")));
Document copy = original.clone();
copy.getParagraphs().set(0, "Modified");
System.out.println(original.getParagraphs()); // Prints: ["Modified", "Content"]
This happens because both Document instances share the same paragraphs list.
Deep Copy Example (with Cloneable)
@Override
public Document clone() {
try {
Document copy = (Document) super.clone();
copy.paragraphs = new ArrayList<>(this.paragraphs); // Deep copy the list
return copy;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
Now, modifying copy.paragraphs won't affect the original object.
This deep copy can also be achieved without Cloneable by writing a custom copy() method. The key is duplicating mutable fields to avoid shared references.
⚖️ Performance vs. Clarity: Should You Use super.clone()?
"The Principle of Least Surprise" says that code should behave in a way that minimizes confusion for those reading or using it. Users should not be surprised by how something works.
Cloneable in Java violates this principle because:
It defines no methods, yet calling
super.clone()depends on it.Forgetting to implement it leads to unexpected
CloneNotSupportedException.Even when it works, it does a shallow copy by default, which can lead to shared internal state.
In short:
super.clone()is fast but potentially confusing.
- Manual
copy()methods are clearer and allow full control.
Use what fits your case:
Go with
clone()for speed and simplicity if you're confident about the structure.Prefer manual copies for clarity, immutability, and safe separation of state.
🔻 Disadvantages of the Prototype Pattern
Complex Cloning Logic: Deep copying can be error-prone and requires careful handling of nested objects, references, and circular dependencies.
Cloning Limitations in Java
Using
clone()(especially in Java) may confuse developers unfamiliar with how shallow vs deep copies work.Java’s
Cloneableinterface is considered flawed:It lacks a
clone()method in the interface.Object.clone()is protected, requiring workarounds.Doesn't support deep cloning out of the box.
Hard to Maintain
- If objects evolve (e.g., new fields), developers must update the cloning logic, increasing maintenance effort and risk of bugs.
Conclusion
The Prototype Pattern lets you create new objects by copying existing ones. It can improve performance, simplify object creation, and reduce coupling to concrete classes.
You can implement it using Java's Cloneable interface and super.clone() or define your own copy methods:
Use
super.clone()when speed matters and your object is simpleUse manual copying when clarity, immutability, or safety is more important
Choose the approach that best matches the complexity and goals of your project.




