Prototype Design Pattern in Java

Learn the Prototype pattern in Java with a newsletter campaign example. Compare shallow and deep copies, copy constructors, Cloneable and prototype registries.

The Prototype pattern creates a new object by copying the state of an existing object. The existing object provides the starting configuration, and a copy operation returns a separate instance. This is useful when an application needs another object with nearly the same settings but must allow the two objects to change independently.

1. How the Prototype Pattern Works

A constructor normally receives the values needed to initialize an object. With Prototype, the caller starts with an object that already has those values and asks it for a copy. The class implements the copying rules, so calling code does not need to read every field and repeat the construction logic.

The existing object is the prototype. Its copy operation creates the new instance, and the code requesting that instance is the client. If several classes support copying, they can declare the operation in a common interface. The client can then request a copy without selecting a constructor for each concrete class. A single class can also provide a copy method directly when the application does not need that abstraction.

The client calls the Prototype copy operation. ConcretePrototype implements that operation and returns a new instance containing copied state.

A new top-level instance does not necessarily have independent mutable fields. If both instances refer to the same mutable list or element, changing that shared data affects both. The copy operation must decide which values can be shared and which nested objects need their own copies.

2. Prototype Pattern in Action

Consider a newsletter editor with a Duplicate campaign feature. An editor prepares a campaign with a sender address, a subject and several content sections. To prepare the next issue, the editor duplicates that campaign, keeps its structure and replaces the content that has changed. Editing the new issue must leave the original campaign intact.

Our Campaign class stores those values. Each nested Section has a heading and an editable body. Campaign.copy() creates another campaign with the same values, and PrototypeDemo is the client that requests the copy and edits it.

First, the application creates a campaign to use as the starting configuration:

Campaign template = new Campaign("news@example.com", "Monthly update",
        List.of(new Campaign.Section("News", "Add this month's news"),
                new Campaign.Section("Events", "Add upcoming events")));

The duplicate operation then creates an October issue and changes its subject and first section:

Campaign october = template.copy();
october.setSubject("October update");
october.section(0).setBody("Our new store opens Friday");

The october campaign retains the sender address and the Events section from template. Its subject and News body now contain the October values. For this to work, copy() must create separate mutable sections, not just a separate Campaign instance. The following implementation makes that ownership explicit.

3. Implementing the Copy Operation

We can implement copying with a copy constructor: a constructor that accepts an existing instance of the same class. Here, copy() calls a private copy constructor, which passes the original values to the ordinary constructor. That constructor creates the campaign’s own list and copies each section into it.

The nested Section class is static, so a section can be created without an existing campaign. Its fields hold the heading and editable body. Calls to Objects.requireNonNull() reject null values before they enter either class. The complete implementation is:

package com.howtodoinjava.patterns.creational.prototype;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public final class Campaign {
    private final String sender;
    private String subject;
    private final List<Section> sections;

    public Campaign(String sender, String subject, List<Section> sections) {
        this.sender = Objects.requireNonNull(sender);
        this.subject = Objects.requireNonNull(subject);
        this.sections = copySections(Objects.requireNonNull(sections));
    }

    private Campaign(Campaign original) {
        this(original.sender, original.subject, original.sections);
    }

    public Campaign copy() {
        return new Campaign(this);
    }

    private static List<Section> copySections(List<Section> source) {
        List<Section> result = new ArrayList<>();
        for (Section section : source) {
            result.add(new Section(Objects.requireNonNull(section)));
        }
        return result;
    }

    public void setSubject(String subject) {
        this.subject = Objects.requireNonNull(subject);
    }

    public String getSender() { return sender; }
    public String getSubject() { return subject; }
    public Section section(int index) { return sections.get(index); }

    @Override
    public String toString() {
        return subject + " | " + sections;
    }

    public static final class Section {
        private final String heading;
        private String body;

        public Section(String heading, String body) {
            this.heading = Objects.requireNonNull(heading);
            this.body = Objects.requireNonNull(body);
        }

        public Section(Section original) {
            this(original.heading, original.body);
        }

        public String getHeading() { return heading; }
        public String getBody() { return body; }

        public void setBody(String body) {
            this.body = Objects.requireNonNull(body);
        }

        @Override
        public String toString() {
            return heading + ": " + body;
        }
    }
}

Calling template.copy() executes new Campaign(this). Inside the copy constructor, original refers to the campaign being copied. The constructor passes its sender, subject and sections to the three-argument constructor. That constructor calls copySections(), which allocates an empty ArrayList and adds a new Section for every original section. The new campaign therefore has both a separate list and separate section instances.

The section copy constructor assigns the existing heading and body values to the new section. Those values are strings. Java’s String class is immutable, so sharing a string reference does not allow either campaign to change that string. When setBody() runs, it assigns a different string reference to the copied section’s body field. The original section’s field is unaffected.

The original and copied campaigns each own a separate list and separate mutable sections.

A shallow copy copies field values, including references to other objects. A deep copy also copies the mutable objects that need to be independent. This implementation makes a deep copy of the campaign’s mutable section data while safely sharing immutable string values. Deep copying does not mean duplicating every immutable value.

We can check the result immediately after the copy and edit calls:

System.out.println("Template: " + template);
System.out.println("Copy: " + october);

The output shows that only the copied campaign changes:

Template: Monthly update | [News: Add this month's news, Events: Add upcoming events]
Copy: October update | [News: Our new store opens Friday, Events: Add upcoming events]

The same copying rule applies when the original campaign is first constructed. The constructor copies the supplied sections instead of retaining the caller’s mutable section instances. After construction, section(index) intentionally returns one of the campaign’s own sections so the editor can modify it. The campaign is mutable; the guarantee is that its copy owns different mutable sections.

This implementation fits a list of sections containing strings. If sections later contain mutable attachments or other nested data, their copy constructors must define how to copy that data too. Resources such as open connections should not be duplicated simply because another field is copied. Cyclic references also need additional handling to avoid copying the same objects indefinitely.

4. Running and Checking the Example

The source directory contains Campaign.java and PrototypeDemo.java. The demo runs the successful duplicate operation, checks the returned references and then demonstrates an incorrect shallow list copy. Use JDK 17 or later; no additional library is required.

From the repository root, compile and run the example:

javac -d out src/main/java/com/howtodoinjava/patterns/creational/prototype/*.java
java -cp out com.howtodoinjava.patterns.creational.prototype.PrototypeDemo

4.1. Checking Independent Copies

After printing the two campaigns, the demo compares their references. Java’s != operator checks whether two references identify different objects. The demo also checks that the sender value is retained:

System.out.println("Different campaigns: " + (template != october));
System.out.println("Different sections: " +
        (template.section(0) != october.section(0)));
System.out.println("Sender retained: " +
        template.getSender().equals(october.getSender()));

Campaign another = template.copy();
System.out.println("Another copy: " + another.section(0).getBody());

These checks produce:

Different campaigns: true
Different sections: true
Sender retained: true
Another copy: Add this month's news

The first two results confirm that the campaign and its first section have different identities. The sender comparison checks equal content rather than identity, because the sender should keep the same value. Finally, another copy of template still contains the original News text. Editing october has not changed the starting configuration used for subsequent copies.

4.2. Why Copying Only the List Is Not Enough

A new list gives us a separate collection, but its elements may still be shared. To see the difference, create a fresh section and copy only the containing list with the ArrayList collection constructor:

List<Campaign.Section> originalSections = new ArrayList<>();
originalSections.add(new Campaign.Section("News", "Original text"));

List<Campaign.Section> copiedList = new ArrayList<>(originalSections);
copiedList.get(0).setBody("Changed through copied list");

System.out.println("Different lists: " + (originalSections != copiedList));
System.out.println("Same section: " +
        (originalSections.get(0) == copiedList.get(0)));
System.out.println("Original section: " + originalSections.get(0).getBody());

The output exposes the shared element:

Different lists: true
Same section: true
Original section: Changed through copied list

The constructor creates a list containing the same element references. Adding or removing an element from copiedList would change only that list, but calling setBody() changes the Section instance that both lists contain. This is why Campaign.copySections() creates each section separately. Copy the mutable elements as well as the collection when each copy must support independent element edits.

5. Choosing a Java Copying Approach

The Prototype pattern describes creating an object from an existing object’s state. It does not require a particular Java API. A copy constructor and a named copy method are one approach; Java also provides Object.clone(), with different rules.

5.1. Copy Constructors and Object.clone()

A copy constructor uses ordinary construction and makes the copying policy visible in the class. Our copy() method gives clients one operation to call and keeps the constructor itself private. The class can validate values, copy nested data and decide which immutable values to retain.

The JDK’s Object.clone() documentation specifies a field-for-field copy. For a reference field, the copied value is the reference; the referenced object is not also cloned. Consequently, calling super.clone() alone on a class with mutable sections would not provide the independence demonstrated above.

The Cloneable interface marks a class as supporting that field-for-field operation. It does not declare a clone() method. A class using this mechanism typically exposes an overridden clone() method and performs any necessary copying of mutable fields itself. Without Cloneable, Object.clone() throws CloneNotSupportedException.

Our Campaign does not implement Cloneable because it never calls Object.clone(). It creates the copy through constructors. Implementing Prototype does not require implementing Cloneable. The constructor-based approach also lets each new campaign initialize its final list field with a separate list. It does not need to replace that field after construction.

5.2. When a Prototype Registry Helps

An application may offer several prepared campaigns, such as a newsletter, a product announcement and an event invitation. A prototype registry stores those starting objects under names so the client can choose one without reconstructing its configuration.

The important part is to copy the selected prototype before editing it. Using the existing template variable, a small registry could look like this:

Map<String, Campaign> templates = Map.of("newsletter", template);
Campaign draft = templates.get("newsletter").copy();
draft.setSubject("November update");

Here, get() retrieves the stored prototype and copy() returns a separately editable campaign. Editing the value returned by get() directly would change the stored prototype itself.

A registry is useful when selection by name is part of the feature; it is unnecessary when the client already has the campaign it wants to duplicate. An unknown name returns null from get(), so production lookup code must handle that result before calling copy().

The source folder includes RegistryDemo, which prints the original and copied subjects so this behavior can be checked independently.

6. Frequently Asked Questions

Copying rules determine whether two campaigns can be edited independently. Field modifiers, performance and alternative creation patterns raise separate questions when applying those rules to another class.

6.1. Does a final Field Make Its Value Immutable?

No. A final reference field cannot be reassigned after initialization, but the object it refers to may still be mutable. In Campaign, the sections field always refers to the same list, while its section bodies can change. The Java Language Specification distinguishes a fixed reference from the state of the referenced object. Independent copies depend on the copying implementation, not just the presence of final.

6.2. Is Prototype Faster Than Creating an Object With a Constructor?

Not necessarily. Copying can avoid repeating expensive work when the original object already holds reusable results, but copying a large object graph also takes time and memory. Our implementation calls constructors and copies every section; it makes no performance claim. Choose it for the duplicate-campaign behavior, and measure the actual application before choosing a copy strategy for speed.

6.3. How Does Prototype Differ From Builder and Factory?

Prototype starts from an existing object’s state. Builder collects construction values or steps to create an object, while a factory encapsulates the creation decision behind an operation. These approaches can coexist. An application might build an initial campaign and later use it as a prototype, but it does not need all three patterns for a simple copy feature.

7. Conclusion

Use Prototype when a new object should begin with an existing configuration. Define the copy operation around the changes the application must allow: share immutable values, copy the mutable data that needs independence, and verify that editing the result leaves the original unchanged.

Happy Learning!

The complete example is available in the GitHub source folder.

Leave a Comment

  1. Why do we have to create new 3 objects (movie, album, show) when loading PrototypeFactory?
    btw everything else is great.

    • because movie, album, and show are different classes and when asked they should return their own instance. what can be done here to make sense is that instead of creating the movie, album and show with a default constructor, A parameterized constructor can be used with some default values.

    • Because the idea of the pattern is to create serval instances of objects using the cloning technique instead of using an instance creation. We want serval objects of these prototype templates: Movie, Album, and Show.

  2. Thank you so much, your design pattern tutorials helped me a lot in understanding design patters.

  3. Your main method should be demonstrating something like

    PrototypeFactory myCopyMaker = new PrototypeFactory();
    
    Movie myMovie = new Movie();
    myMovie.setName("Red Planet");
    
    Movie clonedMovie = myCopyMaker.getInstance(myMovie);
    
    //Verify myMovie and clonedMovie have the same names but they are different objects
    
    

    You need to change your PrototypeFactory class to spit out the copy of given PrototypeCapable object type.

    Your example doesn’t demonstrate the Prototype pattern because it doesn’t copy/clone the object we want to copy/clone. The ‘prototype’ part of the pattern is missing :)

    Imagine a scenario where the object’s state evolves over time and it is significantly different from the creation time. This pattern is handy when you want to make one or several copies of this evolved object.

  4. Hi,
    I didn’t get what is the difference between cloning an object by implementing Clonable(I) normally,and by using prototype pattern.what is the advantages in prototype design pattern over normal cloning process….

    Thanks in advance….

  5. Hi lokesh,
    Can i solve this problem by Movie, Album and Show as a singleton classes(singleton pattern in java)

    Thanks

  6. Very good article,easy to understand the design patterns with real examples and also with perfect class diagram.
    Really useful

  7. I didn’t see much of value of introducing PrototypeCapable interface to enable clone() method, which is build-in in Object. It is not the contract obligation broken on Cloneable in java, it is the implementation was build-in for a purpose to make sure the Clone mechanism is working even across inheritance hierarchy, otherwise, the type inheritance will be mismatch. That means, there is no free way to correctly implement “clone” behavior defined in PrototypeCapable interface other than call “super.clone()” which binding to Object.clone build-in mechanism. What’s the point?

    • In java, to make an object cloning supported, it takes two steps: 1) Implement Cloneable 2) Override clone()

      Lets say, a new developer joined a team which knows only basics in java. He is assigned a task in which he has to add one more object in prototype registry(factory) i.e. “Ticket”.

      By which design, you can force that developer to perform above both steps??

      It is only possible when you tie both steps together otherwise behavior will be surprising.

      • Lokesh,
        So, in other words what you mean to say is the only purpose of this prototype pattern is to introduce an interface which actually has the method clone( ), that somehow Cloneable interface missed in it, so that the rule of overriding the clone( ) method is followed in future.

        That’s the only only only thing in this whole jargon I guess. That’s it. Nothing else. I wonder why we create so many classes and confuse things when all we are doing here is just creating one interface! This whole thing is confusing and not worth the effort.

        By the way, that little java programmer has to learn stuff, we cannot design software components keeping worries of junior programmers in focus. Someone who can spend 1 or 2 hours to understand how java is handling the concept of cloning its objects can easily understand how to override clone and why it’s important to call the clone of super class.

        Frankly, I think this is a design pattern is confusing and not worth studying and remembering, rather programmers should spend time on understanding the basics of cloning in java, that is enough.

        Your thoughts?

        Thanks,
        Saurabh

        • Hi Saurabh, Thanks for sharing your thoughts. Much appreciated.

          I believe that design patterns will feel like a burden if someone will try to apply them forcefully. They are solution of some specific problem. Until you encounter the problem in your design/implementation, you shouldn’t inject patterns just to make code fancy. I agree with you that simplicity is the best thing you can do with your code.

          Regarding prototype design pattern, I do not feel that it is not worth studying. Yes, proper use of clone() will also achieve the target, but you need some code to wrap the actual logic somewhere centralized. So when in future, a junior programmer find a tiny fault in cloning process, he does not have to change it multiple places.

          Remember that if you have to change you code in more than place, to solve one specific problem, then your code need redesign/refactoring.

        • First of all, design patterns are not designed for java only. There is clone() in java but this is not the entire universe of programming.
          Second, clone() works his own specific way, sometimes we want to achive other results and have to get rid of default clone() behaviour.
          Third, I do not agree that ‘keep it simple’ always do the trick. Usually, you ‘keep it simple’ on one level and ‘make it very hard’ on the other: only global look and careful design let you avoid critical difficulties. Applaying design patterns makes code ready to develop and change; avoid problems before even noticed. My expirience tells me, that it really works.

  8. Using an enum for the ModelType is way better, plus strongly typed.

    As a matter of fact, you can do all of this with enums. I love enums ;-)

  9. Dear Lokesh
    ModelType is static subclass of PropertyFactory class ,so Following correction is required in running class TestPropertytypePattern

    String moviePrototype = PrototypeFactory.getInstance(PrototypeFactory.ModelType.MOVIE).toString();

    rest is fine ,I have tested it

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.