skip to content
everything.basics.passByValue

java is pass-by-value

always. even for objects. here's the proof, step by step.

chapter
chapter 02.12
reading time
6 min read

java always passes arguments by value: the method gets a copy of whatever is in the caller's variable. for a primitive, that's a copy of the number. for an object, the variable holds a reference, so the method gets a copy of the reference: it can change the object both arrows point at, but it can never make the caller's variable point somewhere else.

the picture

you know how a method can change an object you pass it, but can't swap two ints? people explain that as "primitives by value, objects by reference". it's a tempting story, and it's wrong. there's one rule, and it covers both.

think of a house and its address on a slip of paper. passing a variable to a method is photocopying the slip and handing over the copy. the method can drive to the house and repaint it (you'll see the new paint, it's the same house). it can scribble a different address on its photocopy. that changes nothing on your slip. your slip still says where your house is.

the proof, step by step

three methods, three experiments. swap gets two ints, fill changes an object's field, replace points its parameter at a new object.

Main.java next to run
  1. 1public class Main {
  2. 2 static class Box {
  3. 3 int value;
  4. 4 Box(int value) { this.value = value; }
  5. 5 }
  6. 6
  7. 7 static void swap(int a, int b) {
  8. 8 int temp = a;
  9. 9 a = b;
  10. 10 b = temp;
  11. 11 }
  12. 12
  13. 13 static void fill(Box b) {
  14. 14 b.value = 99;
  15. 15 }
  16. 16
  17. 17 static void replace(Box b) {
  18. 18 b = new Box(-1);
  19. 19 }
  20. 20
  21. 21 public static void main(String[] args) {
  22. 22 int x = 1, y = 2;
  23. 23 swap(x, y);
  24. 24 Box box = new Box(7);
  25. 25 fill(box);
  26. 26 replace(box);
  27. 27 System.out.println(x + " " + y + " " + box.value);
  28. 28 }
  29. 29}
stdout
·
stack
main
no locals yet
heap
nothing allocated yet.

main starts.

step 1/19
a real recording from the JVM. watch the arrows. at step 11, two arrows lead to one Box, so the change shows up in main. at step 17, replace moved only its own arrow, and the new Box turns into garbage the moment it returns.

the program prints 1 2 99, and every piece of that is the same rule:

  • swap(x, y): a and b are copies of 1 and 2. swapping copies swaps copies.
  • fill(box): b is a copy of the reference. copy or not, it leads to the same Box, and b.value = 99 changes that Box.
  • replace(box): b = new Box(-1) changes what b holds. b is a copy, so box in main never moves.
stack
fill
b
main
x1
y2
box
heap
Box
value99
the whole chapter in one snapshot: inside fill, two separate variables in two separate frames, holding two copies of the same reference. one object. change the object and both see it; change a variable and only that variable changes.

what "by reference" would mean

"pass by reference" is a real thing in other languages: the parameter isn't a copy at all, it's another name for the caller's variable. C++ has it (int&), C# has it (ref). java doesn't, for anything.

if java passed by =
call =
inside the call, after its last line
swap(a, b)
a2
b1
main
x1
y2
what main sees afterwards
x = 1, y = 2

a and b are separate slots holding copies. swapping them is real, but it happens to the copies, and they're thrown away when swap returns.

flip between what java does and what pass-by-reference would do, for both calls. the "reference" side is hypothetical: there's no syntax for it in java. the question to ask is always "is the parameter a separate slot?". in java, it always is.
predict the output
java
void shout(String s) {
    s = s.toUpperCase();
}
 
void main() {
    String name = "duke";
    shout(name);
    IO.println(name);
}
predict the output
java
void reset(int[] arr) {
    arr[0] = 99;
    arr = new int[0];
}
 
void main() {
    int[] nums = {1, 2, 3};
    reset(nums);
    IO.println(Arrays.toString(nums));
}

so how do you "return" more than one thing?

since a method can't reassign the caller's variables, you hand results back instead:

Main.java
record Pair(int first, int second) {}
 
Pair swap(int a, int b) {
    return new Pair(b, a);
}
 
void main() {
    var p = swap(1, 2);
    IO.println(p.first() + " " + p.second());
}
output
2 1

return a value (or a small record holding several), and let the caller decide what to assign. it's also easier to read: you can see the change happening at the call site. the "mutate an object you were passed" style works too, and the JDK uses it (Arrays.sort(a)), but it's easy to surprise people with. immutability is the story of avoiding those surprises.

keylines
  • java passes everything by value: the parameter is a new variable holding a copy.
  • for objects, the copy is a copy of the reference. both arrows lead to one object.
  • changing the object through the parameter is visible to the caller.
  • re-pointing the parameter (param = new ...) never is.
  • want more than one result? return it, in a record if needed.