The Prototype pattern creates new objects by copying an existing instance (prototype) rather than constructing from scratch.

Problem

Object creation is expensive or complex, and you need many similar instances.

Solution

  public class Document implements Cloneable {
    private String title;
    private List<String> pages;

    public Document(String title, List<String> pages) {
        this.title = title;
        this.pages = new ArrayList<>(pages);
    }

    @Override
    public Document clone() {
        try {
            Document copy = (Document) super.clone();
            copy.pages = new ArrayList<>(this.pages); // deep copy mutable fields
            return copy;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

Document original = new Document("Report", List.of("Page 1", "Page 2"));
Document copy = original.clone();
copy.getPages().add("Page 3"); // does not affect original
  

Copy Constructor Alternative

Often cleaner than Cloneable:

  public class Document {
    private final String title;
    private final List<String> pages;

    public Document(Document other) {
        this.title = other.title;
        this.pages = new ArrayList<>(other.pages);
    }
}

Document copy = new Document(original);
  

Java Cloneable Issues

The Cloneable interface is widely considered a design flaw:

  • Shallow copy by default — mutable fields shared
  • No public clone() method in the interface
  • Breaks if subclasses forget to override

Prefer copy constructors or factory methods:

  public static Document copyOf(Document original) {
    return new Document(original.title, new ArrayList<>(original.pages));
}
  

Java Examples

  // Object.clone() — shallow copy
Object copy = original.clone();

// Collections copy
List<String> copy = new ArrayList<>(original);
List<String> copy2 = List.copyOf(original); // immutable copy

// Serialization-based deep copy (legacy approach)
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(original);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
Document deepCopy = (Document) ois.readObject();
  

When to Use

  • Object creation is costly (database query, complex computation)
  • Objects have only a few different configurations
  • You need deep copies of complex object graphs

Best Practices

  • Prefer copy constructors over Cloneable
  • Always deep-copy mutable fields
  • Consider immutable objects — no copying needed
  • Use serialization libraries (Jackson) for deep cloning complex graphs