Java Object Reference Pass by Value: How It Works
java object reference pass by value: Understand how Java passes object references by value, why reassigning a parameter doesn't affect the caller, and how to design me...
When you pass an object to a method in Java, the value of the reference variable is copied into the method's parameter. That means the method receives a copy of the reference, not the object itself. Both the caller and the callee now hold references to the same object in heap memory. This is the essence of java object reference pass by value.
Consider a simple class:
public class Person { private String name; public Person(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return name; } }
Now pass an instance to a method:
public static void changeName(Person p) { p.setName("Alice"); } Person person = new Person("Bob"); changeName(person); System.out.println(person.getName()); // Alice
The method modifies the object's state because p points to the same object as person. The reference value was copied, but the target is shared.
What This Means for Object Mutation
Because the reference is copied, any operation that changes the object's internal state—calling setters, modifying fields, or invoking methods that alter the object—affects the original object. This is often the desired behavior, but it can lead to unexpected side effects if you assume the method works on a copy.
For example, if you pass a List to a method and that method adds an element, the original list changes:
public static void addItem(List<String> list) { list.add("item"); } List<String> items = new ArrayList<>(); addItem(items); System.out.println(items.size()); // 1
The method does not create a new list; it operates on the same list instance.
Primitives vs. Objects: A Side-by-Side Look
Primitive types such as int, double, and boolean are passed by value, meaning the actual value is copied. Modifying a primitive parameter inside a method has no effect on the original variable:
public static void changeInt(int x) { x = 10; } int num = 5; changeInt(num); System.out.println(num); // 5
Objects behave differently because the reference is copied, not the object. This distinction is the source of the common phrase: "Java is pass by value, but for objects, the value is the reference." The reference itself is passed by value, not the object.
Common Pitfall: Trying to Reassign the Reference
A frequent mistake is attempting to reassign the parameter to a new object and expecting the caller's variable to point to that new object. Because the parameter is a copy of the reference, reassigning it only changes the local copy:
public static void replacePerson(Person p) { p = new Person("Charlie"); } Person person = new Person("Bob"); replacePerson(person); System.out.println(person.getName()); // still Bob
The original person still points to the original object. To change which object the caller's variable refers to, you need to return the new reference and assign it, or use a mutable container like an array or a wrapper class.
When You Need to Change the Object Itself
If a method must replace the object entirely, the standard approach is to return the new object and assign it in the caller:
public static Person createNewPerson(String name) { return new Person(name); } Person person = new Person("Bob"); person = createNewPerson("Alice"); System.out.println(person.getName()); // Alice
Alternatively, you can pass an array or a single-element list to hold the reference, but that is less idiomatic and often considered a workaround. The return-value approach is cleaner and more readable.
Memory and Performance Implications
Passing a reference by value does not copy the object, so there is no additional heap allocation or deep copy overhead. The cost is the copy of the reference itself, which is typically a single machine word (4 or 8 bytes). This makes method calls with object parameters inexpensive in terms of memory traffic.
However, if you need to protect the original object from mutation, you must either make the object immutable or pass a defensive copy. Defensive copying can be expensive for large objects, so it should be used only when necessary, such as when exposing internal collections.
How This Affects API Design
Understanding reference semantics is crucial when designing method signatures. If a method should not modify the input object, document that expectation or make the object immutable. If the method is expected to mutate, ensure the caller is aware. For example, Collections.sort(List<T>) mutates the list in place, while List.copyOf returns an unmodifiable copy.
When returning objects from a method, consider whether the caller should be able to modify the returned object. Returning a reference to an internal field can break encapsulation. In such cases, return a copy or an unmodifiable view.
This behavior also affects equality checks and caching. Since references are passed by value, two variables can point to the same object, and comparing them with == checks identity, not equality. Always use equals() for value comparison.