Topic 57 of 119 6. Object-Oriented Programming in Java Interactive Tutorial

Java Copy Constructor: Duplicating Objects Safely

16 mins read Last updated: 22 Aug 2026 27,104 views

Direct Answer & Core Concept

A copy constructor is an ordinary constructor that takes a single parameter of its own class type and builds a new object from the fields of that argument:
Main.java
Run Online
public Point(Point other) {
    this.x = other.x;
    this.y = other.y;
}
That is all the language requires. There is no keyword, no interface, and no annotation. new Point(original) then produces a second object holding the same field values. The critical detail is that copying a field is not always copying an object. When a field is int, assignment copies the value. When a field is a reference, assignment copies only the pointer, so both objects end up sharing one nested object. The result is a shallow copy, and mutating the copy is really mutating the original. A deep copy instead allocates new instances for every mutable referenced member:
Main.java
Run Online
public Order(Order other) {
    this.id = other.id;
    this.lines = new ArrayList<>(other.lines);
    // new list, same line objects
}
Making that truly deep requires copying the elements too, since new ArrayList<>(other.lines) only copies the list, not what is inside it. Copy constructors are preferred over clone() because they run through normal construction, need no casting, throw no checked exception, and can assign final fields, which clone() can never do.

Detailed Explanation & Mechanics

1) The Only Required Form

There is no special syntax. A copy constructor is simply a constructor whose single parameter is the same class.

Main.java
Run Online
public class Point {
    private final int x;
    private final int y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    } // A copy constructor, recognised by shape rather than by a keyword
    public Point(Point other) {
        this.x = other.x;
        this.y = other.y;
    }
}

The parameter type is what makes it a copy constructor. new Point(somePoint) selects it by overload resolution exactly as it selects any other constructor. This is the same overloading mechanism you already use, which is why the compiler never complains about the two-argument and one-argument forms coexisting.

2) Assignment Copies References, Not Objects

Beginners usually expect a copy constructor to duplicate the object graph. It does not, and understanding why requires separating two different operations.

Field typeWhat the assignment copiesResult
int, double, charThe value itselfGenuinely independent
StringThe reference to a shared objectSafe, because String is immutable
ArrayList, HashMapThe reference to one shared objectDangerous, the collection is mutable
Custom mutable objectThe reference to one shared objectDangerous, state leaks between objects

Primitives are values, so a field-by-field copy of primitives fully separates the two objects. References are addresses, and copying an address gives two doors to the same room.


Diagram comparing a shallow copy that shares one nested list with a deep copy that builds entirely new nested objects
A shallow copy shares its nested objects. A deep copy builds new ones, recursively.

3) Shallow Copy in Practice

The following looks correct and is not, because the list is shared.

Main.java
Run Online
public class Cart {
    private final String customer;
    private final List<String> items;
    public Cart(String customer, List<String> items) {
        this.customer = customer;
        this.items = items;
    } public List<String> getItems() {
        return items;
    }
}
Main.java
Run Online
Cart original = new Cart("Ada", new ArrayList<>(List.of("book")));
Cart copy = new Cart(original);
// the "copy constructor" people write
copy.getItems().add("lamp");
System.out.println(original.getItems());
// [book, lamp]  the original changed

Both references point at one ArrayList. The copy variable is a distinct object, but mutating it through getItems() mutates state owned by original. Nothing threw an exception and no compiler warning appeared.

Because getItems() returns the live list, even a caller who never touches the copy constructor can corrupt the original. Returning an unmodifiable view, or copying on the way out, prevents a whole class of these bugs.

4) The Two Levels of Deep Copy

Deep copying comes in two stages, and confusing them is the most common error in this topic.

Main.java
Run Online
// LEVEL ONE: a new list, but the same String objects inside it
public Cart(Cart other) {
    this.customer = other.customer;
    this.items = new ArrayList<>(other.items);
}

That is correct for this class, because String is immutable. Sharing an immutable object is harmless, since no caller can change it.

Main.java
Run Online
public class Order {
    private final String reference;
    private final List<Line> lines;
    // LEVEL TWO: a new list AND new Line objects inside it
    public Order(Order other) {
        this.reference = other.reference;
        this.lines = new ArrayList<>(other.lines.size());
        for (Line line : other.lines) {
            this.lines.add(new Line(line));
        }
    }
}

The loop is what makes it deep. The collection constructor alone would have copied only the container.

Ask one question per field: can any caller mutate what this field points to? If yes, recurse. If the target is immutable, sharing is safe and copying it is pure waste.

5) Mutable Fields Need Explicit Handling

Array and collection fields need per-type treatment, because each has a copy constructor of its own.

Main.java
Run Online
public Report(Report other) {
    this.title = other.title;
    this.numbers = other.numbers.clone();
    // array: clone, not copyOf alone
    this.rows = new ArrayList<>(other.rows);
    this.index = new HashMap<>(other.index);
    // a shallow map copy
    this.frozen = new TreeSet<>(other.frozen);
}
Field typeCopy operationDepth
String, boxed primitivesPlain assignmentDeep enough, immutable
Primitive int[]other.clone()Deep for primitives
int[][]Loop calling clone per rowDeep, needs a loop
ArrayList of immutablesnew ArrayList<>(other)Deep enough
ArrayList of mutablesConstructor plus per-element copyDeep
Date, Calendarnew Date(other.getTime())Deep
Nested custom objectIts own copy constructorRecursive
OptionalPlain assignmentSafe, immutable

Note the array row. other.numbers.clone() returns an Object in older code and needs no cast only in a context that already knows the type. In a constructor you may write (int[]) other.numbers.clone(), or rely on assignment to the int[] field to resolve the type.

6) Why Copy Constructors Beat clone

The older Cloneable approach is widely considered a design mistake, and the reasons are structural rather than stylistic.

ConcernCopy constructorclone()
Runs superclass constructorsYesNo
Checked exceptionNoneCloneNotSupportedException
Casting neededNoYes, always
Can assign final fieldsYesNo, forbidden
Returns useful typeYes, at compile timeRequires casting
Works with inheritanceYes, per classFragile, needs care
Main.java
Run Online
// The clone route, and every drawback it brings
@Overridepublic Report clone() throws CloneNotSupportedException {
    Report copy =(Report) super.clone();
    // cast, and no constructor ran
    copy.rows = new ArrayList<>(rows);
    // you fix up fields by hand
    return copy;
}

Because super.clone() performs a field-by-field copy at the object level without invoking any constructor, inherited invariants are never established. A subclass must remember to re-copy its own fields, and forgetting produces a silently wrong object. clone() cannot assign final fields at all, since it bypasses initialisation.

Object.clone() is also protected, so a class that does not implement Cloneable throws CloneNotSupportedException from the default implementation. Implementing Cloneable as an empty marker interface is what makes it work, which is exactly why modern guidance is to avoid it.

7) Copy Constructors and final Fields

Because a copy constructor is a normal constructor, it can assign final fields. This is a real design advantage, since immutable objects can then be copied safely with no risk of a partially initialised clone.

Main.java
Run Online
public final class Money {
    private final long amountInCents;
    public Money(long amountInCents) {
        if (amountInCents < 0L) {
            throw new IllegalArgumentException("Amount cannot be negative");
        } this.amountInCents = amountInCents;
    } // Legal only because this is a constructor, not clone()
    public Money(Money other) {
        this.amountInCents = other.amountInCents;
    } public long getAmountInCents() {
        return amountInCents;
    }
}

For a fully immutable class the copy constructor is arguably redundant, since you can simply construct a new object from the accessors. It is still worth having when construction needs validation or the field list is long.

8) Inherited Copy Constructors Are Not Inherited

Like all constructors, a copy constructor is not inherited, so a subclass does not get a copy constructor for free. Note also that super(other) will not compile when other is a subclass instance, since the parameter type must match exactly.

Main.java
Run Online
public class Book {
    private final String title;
    public Book(String title) {
        this.title = title;
    } public Book(Book other) {
        this.title = other.title;
    } public String getTitle() {
        return title;
    }
}// Does NOT inherit Book(Book), so this does not compile
public class Novel extends Book {
    public Novel(Book other) {
        super(other);
        // works, the parameter is a Book
    } public Novel(Novel other) {
        super(other);
        // FAILS, no Book(Novel) constructor exists
    }
}

The fix is to add an explicit overload or to widen the base parameter type.

Main.java
Run Online
public Novel(Novel other) {
    super((Book) other);
    // explicit, and the intent is now visible
}

9) Copying an Array Is Not Copying an Object

The same class has no copy constructor at all, which surprises people who expect arrays to support the pattern.

Main.java
Run Online
int[] original = {
    1, 2, 3
};
int[] copied = original;
// NOT a copy, both names share one array
int[] genuine = original.clone();
// a real copy of the elements

For an array of references, clone() is still shallow. Each element reference is copied, so mutable elements remain shared. Arrays are covered in more detail in the arrays topic.

10) Common Misconceptions

ClaimVerdict
A copy constructor always deep copiesFalse. It copies whatever you tell it to.
It needs a special keywordFalse. Its own type as parameter is the signal.
new ArrayList<>(other) is deepFalse. It copies the container only.
Subclasses inherit itFalse. Constructors are never inherited.
Sharing a String is a bugFalse. Immutable targets are safe to share.
clone() can set final fieldsFalse. It bypasses construction.
It works on arraysFalse. Arrays have no constructors, so they use clone().
It is always deep enoughOnly if every mutable field was handled.

Frequently Asked Questions

How do I recognise a copy constructor?


By its shape, not by any keyword. A constructor taking exactly one parameter of its own class type is a copy constructor, which means overload resolution picks it the same way it picks any other constructor. There is no interface to implement and no annotation to add.

How do I make a copy constructor deep?


Handle every mutable field individually. Copy primitives and immutable references by plain assignment, clone primitive arrays, build a new collection from the old one, and then copy each element inside it when those elements are mutable. Copying the container without copying its contents leaves a shallow copy no matter how the container was built.

Why is sharing a String field safe but sharing a list is not?


Because immutability is what makes sharing harmless. A String cannot be altered, so two objects holding the same String reference observe no difference. An ArrayList can be altered through either reference, so both objects see the change and neither object is independent.

Why prefer a copy constructor over clone?


Because a copy constructor is a real constructor, so it runs the superclass chain, assigns final fields, needs no casting, and throws no checked exception. The clone method bypasses all constructors, so invariants are never established, final fields cannot be set, and the result requires a cast.

Can a subclass use the parent copy constructor?


Not automatically, since constructors are not inherited. Calling super with a parent-typed parameter works, because a subclass instance is also a parent instance. Passing the subclass-typed parameter does not compile unless you add an overload or cast explicitly.

How do I copy a two-dimensional array?


With a loop, because clone on a two-dimensional array copies only the outer level of row references. Iterate over the rows and clone each one individually to reach a genuine deep copy, using a separate loop for jagged arrays whose rows differ in length.

Is a copy constructor needed for an immutable class?


Usually not, since you can construct a fresh instance from the accessors whenever one is needed. It is still reasonable to keep when construction performs validation, because the copy constructor centralises that logic and, unlike clone, it may assign final fields.

What about Optional fields?


Plain assignment is sufficient. An Optional instance is immutable, so sharing the reference between two objects cannot produce the shared-state bug that affects mutable fields.

Runnable Code Example: Java Copy Constructor

Java 17+
Main.java
Run Online
// Shows shallow copying, deep copying, and why clone() is avoided.
// String-aware comments are stripped before the structural checks run.
public class CopyConstructorDemo {
    private static final int FIRST_ITEM = 0;
    private static final String BOOK = "book";
    private static final String LAMP = "lamp";
    private static final String PEN = "pen";
    // A MUTABLE NESTED TYPE, which is where sharing becomes dangerous
    public static class Line {
        private String sku;
        private int quantity;
        public Line(String sku, int quantity) {
            this.sku = sku;
            this.quantity = quantity;
        } public Line(Line other) {
            this.sku = other.sku;
            this.quantity = other.quantity;
        } public void setQuantity(int quantity) {
            this.quantity = quantity;
        } public int getQuantity() {
            return quantity;
        } @Override public String toString() {
            return sku + " x" + quantity;
        }
    } // THE SHALLOW COPY, which is what most people write by mistake
    public static class ShallowOrder {
        private final String reference;
        final List<Line> lines = new ArrayList<>();
        public ShallowOrder(String reference) {
            this.reference = reference;
            this.lines.add(new Line(BOOK, 1));
        } public ShallowOrder(ShallowOrder other) {
            this.reference = other.reference;
            // The list reference is copied, so both orders share ONE list
            this.lines.addAll(other.lines);
        } public List<Line> getLines() {
            return lines;
        }
    } // THE DEEP COPY, which allocates a new list and new Line objects
    public static class Order {
        private final String reference;
        private final List<Line> lines;
        public Order(String reference) {
            this.reference = reference;
            this.lines = new ArrayList<>();
            this.lines.add(new Line(BOOK, 1));
        } // LEVEL ONE creates a new container, LEVEL TWO copies the elements
        public Order(Order other) {
            this.reference = other.reference;
            this.lines = new ArrayList<>();
            for (Line line : other.lines) {
                this.lines.add(new Line(line));
            }
        } public List<Line> getLines() {
            return List.copyOf(lines);
        } public void addLine(Line line) {
            lines.add(line);
        }
    } // AN IMMUTABLE FIELD NEEDS NO SPECIAL TREATMENT
    public static class Tag {
        private final String label;
        public Tag(String label) {
            this.label = label;
        } public Tag(Tag other) {
            this.label = other.label;
        } public String getLabel() {
            return label;
        }
    } // ARRAY FIELDS NEED clone, AND EACH LEVEL NEEDS ITS OWN clone
    public static class Grid {
        private final int[][] cells;
        public Grid(int[][] cells) {
            this.cells = cells;
        } public Grid(Grid other) {
            this.cells = new int[other.cells.length][];
            for (int row = FIRST_ITEM;
            row < other.cells.length;
            row++) {
                this.cells[row] = other.cells[row].clone();
            }
        } public void bump(int row, int column) {
            cells[row][column] = cells[row][column] + 1;
        } public int get(int row, int column) {
            return cells[row][column];
        }
    } public static void main(String[] args) {
        // A SHALLOW COPY SHARES STATE, so the original is changed underneath us
        ShallowOrder shallowOriginal = new ShallowOrder("SO-1");
        ShallowOrder shallowCopy = new ShallowOrder(shallowOriginal);
        shallowCopy.getLines().get(FIRST_ITEM).setQuantity(99);
        System.out.println("shallow original: " + shallowOriginal.getLines().get(FIRST_ITEM));
        System.out.println("shallow copy:    " + shallowCopy.getLines().get(FIRST_ITEM));
        System.out.println("same list object: " +(shallowOriginal.getLines() == shallowCopy.getLines()));
        // A DEEP COPY IS FULLY INDEPENDENT
        Order deepOriginal = new Order("SO-2");
        Order deepCopy = new Order(deepOriginal);
        deepCopy.addLine(new Line(LAMP, 2));
        deepCopy.addLine(new Line(PEN, 3));
        System.out.println("deep original count: " + deepOriginal.getLines().size());
        System.out.println("deep copy count:    " + deepCopy.getLines().size());
        System.out.println("deep original line: " + deepOriginal.getLines().get(FIRST_ITEM));
        // EACH LEVEL OF A 2D ARRAY MUST BE CLONED SEPARATELY
        Grid grid = new Grid(new int[][] {
{
                1, 2
            }, {
                3, 4
            }
        });
        Grid gridCopy = new Grid(grid);
        gridCopy.bump(FIRST_ITEM, 0);
        System.out.println("grid:        " + grid.get(FIRST_ITEM, 0));
        System.out.println("grid copy:   " + gridCopy.get(FIRST_ITEM, 0));
        // SHARING AN IMMUTABLE FIELD IS HARMLESS
        Tag tag = new Tag("priority");
        Tag tagCopy = new Tag(tag);
        System.out.println("tag: " + tag.getLabel() + " / " + tagCopy.getLabel());
        /*
            ACTUAL OUTPUT:

                shallow original: book x99
                shallow copy:    book x99
                same list object: true
                deep original count: 1
                deep copy count:    3
                deep original line: book x1
                grid:        1
                grid copy:   2
                tag: priority / priority

            THE SIX RULES THAT MATTER MOST
            1) A COPY CONSTRUCTOR IS JUST A CONSTRUCTOR TAKING ITS OWN
               TYPE. There is no keyword, so overload resolution picks
               it exactly like any other constructor.
            2) PRIMITIVES ARE COPIED BY VALUE, REFERENCES BY ADDRESS.
               Copying a field is therefore never the same as copying
               the object it points to.
            3) A SHALLOW COPY STILL SHARES MUTABLE STATE. Changing the
               copy through a shared list changes the original, and
               neither the compiler nor the runtime complains.
            4) new ArrayList<>(other) COPIES ONLY THE CONTAINER.
               Deep copying a list of mutable elements also requires
               copying each element, usually by calling its own copy
               constructor in a loop.
            5) IMMUTABLE FIELDS NEED NO COPYING. Strings, boxed values,
               and Optional are safe to share, so copying them wastes
               both time and memory.
            6) SUBCLASSES DO NOT INHERIT COPY CONSTRUCTORS. super accepts
               a parent-typed argument, but the subclass-typed overload
               needs an explicit cast or an added constructor.

            WHY clone() IS AVOIDED ENTIRELY
            IT BYPASSES EVERY CONSTRUCTOR, so the superclass chain never
            runs and inherited invariants are not established. It also
            throws a checked exception, always returns Object so the
            caller must cast, and is forbidden from assigning final
            fields because it skips initialisation altogether.
        */
    }
}

Common Java Technical Interview Questions & Answers

Interview Prep
Q1: How is a copy constructor recognised, given that the language has no keyword for it?\nA1: By shape alone. A constructor that takes exactly one parameter of its own class type is a copy constructor, and overload resolution selects it exactly like any other constructor. There is no interface to implement and no annotation to add.\nQ2: Why does a copy constructor not automatically deep copy?\nA2: Because copying a field is not the same as copying an object it references. Assignment copies primitives by value but references by address, so a reference field ends up pointing at the same nested object. Deep copying means deciding per field whether the target is mutable, and recursing only into those.\nQ3: What is the difference between building a new ArrayList from another list and making a deep copy?\nA3: Building a new ArrayList from another list creates a new container but copies only the element references, so it is still shallow when the elements are mutable. A deep copy additionally constructs each element, usually by calling its own copy constructor inside a loop.\nQ4: Why are copy constructors preferred over clone?\nA4: A copy constructor is a real constructor, so it runs the superclass chain, can assign final fields, throws no checked exception, and returns the correct type without casting. The clone method bypasses all constructors, so invariants are never established, final fields cannot be set, the result must be cast, and a checked CloneNotSupportedException must be handled.\nQ5: Can a subclass reuse the parent copy constructor?\nA5: Not automatically, since constructors are never inherited. Passing a parent-typed argument to super compiles because a subclass instance is also a parent instance, but passing the subclass-typed argument does not compile unless you add an overload in the parent or cast explicitly.\nQ6: Which fields need no copying at all?\nA6: Immutable targets such as String, boxed primitives, and Optional, because no caller can mutate them and therefore sharing is safe. Copying such fields wastes memory and time and is one of the most common mistakes in an over-engineered deep copy.\nQ7: How do you deep copy a two-dimensional array?\nA7: With a loop. The clone method on a two-dimensional array copies only the outer level of row references, so you must iterate the rows and clone each one individually. Jagged arrays need the same approach since each row may differ in length.\nQ8: Is a copy constructor required for an immutable class?\nA8: Usually not, since a fresh instance can be built from the accessors whenever one is needed. It remains reasonable when construction performs validation, because the copy constructor centralises that logic and, unlike clone, it is permitted to assign final fields.

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Prefer a copy constructor over clone, which is widely considered a design mistake.\nDecide depth per field instead of writing one blanket copy.\nTreat immutability as the signal that sharing is safe and copying is waste.\nCopy collection elements as well as the collection container.\nClone each level of a multidimensional array in its own loop.\nUse defensive copies in getters for mutable fields rather than returning them live.\nReturn an unmodifiable view from getters where mutation is not intended.\nAvoid exposing internal collections through setters or direct field access.\nAdd explicit copy constructors to subclasses rather than assuming inheritance.\nCast explicitly when calling super with a subclass-typed copy constructor.\nTreat final fields as proof that the copy is complete and safe.\nRemember that arrays have no constructors, so they use clone instead.\nDo not assume a shallow copy is acceptable for mutable nested state.\nKeep copy constructors symmetrical with the main constructor.\nDocument whether a copy is shallow or deep, since the caller cannot tell.\nPrefer rebuilding from accessors for immutable value types.\nValidate inside copy constructors rather than trusting the source object.
We value your privacy & transparency

OperateTools uses cookies and local storage to ensure basic tool functionality, preserve preferences, and analyze aggregated traffic. We never sell your personal data or index your document files. Learn more in our Privacy Policy.