W3docs

Java Pass-by-Value vs. Pass-by-Reference

Why Java is always pass-by-value, even when passing object references, and what this means in practice.

Java is pass-by-value. Always. Whatever you pass to a method — an int, a String, a custom object, an array — the method receives a copy of the value you handed it. That value might be a number, or it might be a reference to an object, but it's still a copy.

This confuses a lot of people because a method can change the contents of an object you passed in, and that change is visible to the caller. So it feels like the object was passed by reference. It wasn't. What was passed was a copy of the reference. This chapter shows exactly what that means.

Primitives are easy

Passing a primitive copies its value into the parameter:

public static void doubleIt(int n) {
  n = n * 2;
}

int x = 5;
doubleIt(x);
System.out.println(x);    // 5

The method has its own n, separate from x. Reassigning n has no effect on x. Everyone agrees this is pass-by-value.

Object arguments: the reference is copied

For object types, the variable doesn't contain the object — it contains a reference (an address pointing to the object somewhere in memory). When you pass that variable to a method, Java copies the reference, not the object:

Caller's variable        →   [ref to Dog A]
                                  |
                                  v
                              { Dog A: name="Rex" }
                                  ^
                                  |
Method's parameter       →   [ref to Dog A]

Both variables now point at the same Dog object. That's why mutating the object through the parameter is visible at the call site:

public static void rename(Dog d) {
  d.setName("Buddy");           // mutates the shared object
}

Dog rex = new Dog("Rex");
rename(rex);
System.out.println(rex.getName());   // Buddy

But assigning a new reference to the parameter does not change the caller's variable:

public static void replace(Dog d) {
  d = new Dog("Buddy");         // parameter now points at a new Dog
}

Dog rex = new Dog("Rex");
replace(rex);
System.out.println(rex.getName());   // Rex — unchanged

The method updated its own copy of the reference. The caller's rex still points to the original Dog.

The two-pointer mental model

Whenever a method takes an object parameter, visualize two arrows at the start: one from the caller's variable, one from the method's parameter, both pointing at the same object.

  • Mutating the object through either arrow changes what the other arrow sees.
  • Reassigning one arrow to point somewhere else has no effect on the other arrow.

That single rule resolves every "is Java pass-by-reference?" question.

Arrays follow the same rule

Arrays are objects, so passing one to a method copies the reference, not the contents:

public static void zeroFirst(int[] xs) {
  xs[0] = 0;                    // mutates the shared array
}

int[] data = {1, 2, 3};
zeroFirst(data);
System.out.println(data[0]);    // 0

The method changed an element through its copy of the reference, and the caller sees the change because both references point at the same array.

But reassigning the parameter to a brand-new array has no effect on the caller:

public static void resetArray(int[] xs) {
  xs = new int[]{0, 0, 0};      // parameter only
}

int[] data = {1, 2, 3};
resetArray(data);
System.out.println(data[0]);    // 1 — unchanged

Strings: immutability hides the issue

Strings are objects too, but they're immutable — there's no method that changes a String's contents. So the mutation case can't happen:

public static void uppercase(String s) {
  s = s.toUpperCase();          // creates a new String
}

String name = "ada";
uppercase(name);
System.out.println(name);       // ada — unchanged

s.toUpperCase() returns a new String; assigning it to s only updates the parameter. name still points at the original "ada". To "change" a string, return the new one and let the caller assign it.

Why this matters

Three practical consequences:

  1. A method can't "out" a primitive or reassign the caller's reference. If you need that effect, return the new value: x = doubleIt(x); or use a wrapper object the caller can read after the call.

  2. A method can mutate a shared object — which is sometimes what you want (filling an array, populating a list) and sometimes a surprise (callers don't expect their list to change).

  3. Defensive copying. If a method shouldn't change the caller's object, either don't mutate the parameter, or copy it first: Arrays.copyOf(xs, xs.length). Conversely, if you return an internal array or list, callers can mutate it through the reference unless you return a copy.

A worked example

java— editable, runs on the server

What's next

You understand how single arguments flow into methods. Sometimes you don't know up front how many arguments the caller will supply — think String.format, or a max(...) that takes any number of values. That's what varargs are for.

Practice

Practice

A method receives an object parameter and reassigns it: param = new Thing(). What does the caller see?