Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java pass-by-value: references and mutation

Last updated: 30 Sept 20263 min read
tutorial
IntermediateBy AITrove Editorial

Java passes argument values into method parameters; for an object argument, the copied value is a reference to that object.

A copied reference still reaches one object

The caller and callee can hold different variables whose values point to the same mutable list. Mutating that list through either reference changes the shared object. Reassigning the callee’s parameter only changes the callee’s local variable.

No caller variable is rebound by that reassignment. The parameter is not an alias for the caller’s variable slot. This distinction explains why a method can append to a list but cannot replace the caller’s list by assigning a new one to its parameter.

Primitive arguments are copied too. Incrementing an int parameter does not update the caller’s int. Wrapping the int in a mutable object permits shared mutation of that object, but does not change the parameter-passing rule.

Choose an ownership boundary

The program deliberately combines a mutation and a local reassignment. Only the mutation survives when observed by the caller. The second addition lands in a new list that the caller never receives.

A method named enrich should document whether it changes its input or returns another result. If the input must remain unchanged, copy the list and return that copy. A shallow copy separates list structure, not mutable element objects.

Returning a result is often clearer than assigning to parameters. It makes the new owner visible at the call site and supports tests that verify both the output and the unchanged input. For more on copying costs, read array-backed list ownership.

Working program

Java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class DeliveryArgumentOwnership {
    static void appendThenRebind(List<String> events) {
        events.add("packed");
        events = new ArrayList<>();
        events.add("local-only");
    }
    public static void main(String[] args) {
        List<String> deliveryEvents = new ArrayList<>(Arrays.asList("received"));
        appendThenRebind(deliveryEvents);
        System.out.println(deliveryEvents);
    }
}

Output

Output
[received, packed]

Cost and design choices

Copying a reference is constant work. Copying n list element references into a new ArrayList requires O(n) time and O(n) reference storage. Deep copying depends on the contents and cannot be described by the list length alone.

A shared reference can keep a large object reachable even when the original owner drops its own reference. Garbage collection follows reachability, not the apparent age of a local variable.

This example has one thread. A shared mutable reference also creates a concurrency question when threads are involved; pass-by-value does not prevent races or establish visibility.

Common Mistakes

  • Do not call Java pass-by-reference.
  • Do not expect parameter reassignment to replace the caller’s object variable.
  • Do not describe a shallow collection copy as a copy of every element object.

Connect the contracts

Compare the boundary explained in Controlling mutable state with the assumptions made by this program.

Compare the Python boundary

Python variables: names refer to objects, assignment does not copy.

java
pass-by-value
Storage details