Java 4 min read

The Prototype Design Pattern: Copy Don't Create

Master the Prototype Pattern in Java. Learn how to clone objects efficiently instead of creating expensive instances from scratch.

MR

Moshiour Rahman

Advertisement

The Problem: Expensive Object Creation

Imagine creating a DatabaseConnection object requires:

  1. Opening a socket (100ms)
  2. Authenticating (200ms)
  3. Loading configuration (50ms)

If you need 100 similar connections with slightly different configs, creating each from scratch takes 35 seconds total.

for (int i = 0; i < 100; i++) {
    DatabaseConnection conn = new DatabaseConnection(); // 350ms each!
    conn.setPort(8080 + i);
}

The Solution: The Prototype Pattern

The Prototype Pattern creates new objects by copying an existing object (prototype) instead of creating from scratch.

Real-Life Analogy: Xerox Machine 📄

Instead of writing 100 identical letters by hand:

  1. Write one perfect letter (the prototype)
  2. Photocopy it 100 times (each copy takes 2 seconds vs 5 minutes to write)

Result: 100 letters in 3 minutes instead of 8 hours.

Visualizing the Pattern

Prototype Pattern

Implementation

1. The Prototype Interface

Java provides Cloneable interface, but we’ll make our own for clarity:

public interface Prototype {
    Prototype clone();
}

2. Concrete Prototype with Shallow Copy

public class Shape implements Cloneable {
    private String id;
    private String type;
    private int x, y;

    public Shape(String type) {
        this.type = type;
        // Expensive initialization
        loadFromDatabase();
    }

    private void loadFromDatabase() {
        // Simulating expensive operation
        System.out.println("Loading " + type + " from DB (500ms)...");
    }

    // Shallow clone
    @Override
    public Shape clone() {
        try {
            return (Shape) super.clone(); // Shallow copy
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }

    // Getters and setters
    public void setX(int x) { this.x = x; }
    public void setY(int y) { this.y = y; }
}

3. Deep Copy for Complex Objects

public class Employee implements Cloneable {
    private String name;
    private Address address; // Reference type!

    @Override
    public Employee clone() {
        try {
            Employee cloned = (Employee) super.clone();
            // Deep copy the address
            cloned.address = this.address.clone();
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

class Address implements Cloneable {
    private String street;
    private String city;

    @Override
    public Address clone() {
        try {
            return (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

Usage

// Expensive creation (once)
Shape circlePrototype = new Shape("Circle");
// Output: Loading Circle from DB (500ms)...

// Fast cloning (cheap)
Shape circle1 = circlePrototype.clone();
circle1.setX(10);

Shape circle2 = circlePrototype.clone();
circle2.setX(20);

// No database loading! Instant copies.

In The Wild (Real World Examples)

1. Object.clone() in Java

The classic example. Every object can override clone():

ArrayList<String> original = new ArrayList<>();
original.add("A");

ArrayList<String> copy = (ArrayList<String>) original.clone();

2. Spring Bean Scopes

Spring’s @Scope("prototype") creates a new bean instance by cloning a prototype:

@Component
@Scope("prototype")
public class PrototypeBean {
    // New instance returned each time
}

3. StringBuilder Copy Constructor

StringBuilder sb1 = new StringBuilder("Hello");
StringBuilder sb2 = new StringBuilder(sb1); // Copies content

Shallow vs Deep Copy

TypeWhat It CopiesUse When
ShallowPrimitive fields + references (not content)Objects with only primitives/immutables
DeepEverything, including referenced objectsObjects contain mutable references

Shallow Copy Pitfall

class Team implements Cloneable {
    String name;
    List<String> members; // Reference type

    @Override
    public Team clone() {
        return (Team) super.clone(); // SHALLOW - shares members list!
    }
}

Team team1 = new Team("Engineering");
team1.members.add("Alice");

Team team2 = team1.clone();
team2.members.add("Bob"); // Also adds to team1! 😱

Solution: Deep copy the list:

@Override
public Team clone() {
    Team cloned = (Team) super.clone();
    cloned.members = new ArrayList<>(this.members);
    return cloned;
}

Cheat Sheet

FeatureDetails
CategoryCreational
Problem SolvedExpensive object creation, complex initialization
Key implementationclone() method returns a copy
ProsPerformance (avoid expensive init), Flexibility (runtime configuration)
ConsComplexity (deep vs shallow copy), Cloneable issues (checked exception)

Tips to Remember 🧠

  • “Xerox”: Think of photocopying instead of rewriting.
  • “Shallow vs Deep”: Shallow copies references; deep copies content.
  • Java Caveat: Cloneable is a marker interface and throws checked exception. Consider copy constructors or factory methods as alternatives.

Advertisement

MR

Moshiour Rahman

Software Architect & AI Engineer

Share:
MR

Moshiour Rahman

Software Architect & AI Engineer

Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.

Related Articles

Comments

Comments are powered by GitHub Discussions.

Configure Giscus at giscus.app to enable comments.