Java is always pass-by-value
Primitives copy the value; references copy the reference (object can be mutated, variable cannot be re-pointed).
Always a copy
Java has exactly one rule: it always passes a copy of the argument's value (pass-by-value). For a primitive like int, that's a copy of the number, so nothing the method does can reach the caller's variable.
static void bump(int n) {
n++; // only the copy
}
int n = 5;
bump(n); // n is still 5Copying a reference
An object variable doesn't hold the object; it holds a reference, like a house address. Passing it copies the address: two addresses, one house. So if the method changes the object (sb.append), the caller sees it.
static void addX(StringBuilder sb) {
sb.append("X"); // same object
}
var s = new StringBuilder("hi");
addX(s);
// s is now "hiX"Your turn
What prints?
static void addItem(StringBuilder cart) {
cart.append("+apple");
}
void main() {
var c = new StringBuilder("bread");
addItem(c);
System.out.println(c);
}breadbread+apple+apple
Show the answer
cart and c are two references to the same StringBuilder. append changes that one object, so main sees bread+apple.
Re-pointing the copy
Assigning a new object to the parameter only changes the method's copy of the reference. The caller's variable still points to the original. Mutating the object is visible; reassigning the parameter never is.
static void addX(StringBuilder sb) {
sb.append("X"); // caller sees it
sb = new StringBuilder(); // re-point
sb.append("Y"); // caller never sees
}Your turn
What prints?
static void reset(StringBuilder sb) {
sb = new StringBuilder();
sb.append("new");
}
void main() {
var s = new StringBuilder("old");
reset(s);
System.out.println(s);
}newoldoldnew
Show the answer
old. sb first pointed at the caller's object, then was re-pointed to a fresh one. Only that new object got "new", and it vanished with the method. s still points to the original.
Array vs int
Both values start at 4. What prints?
static void grow(int[] a, int n) {
a[0] *= 2;
n *= 2;
}
void main() {
int[] arr = {4};
int n = 4;
grow(arr, n);
System.out.println(arr[0] + " " + n);
}8 84 48 44 8
Show the answer
The array reference is copied, but both copies point to the same array, so a[0] *= 2 is visible. The int n is a plain value copy, so main's n stays 4.
Two classic puzzles
Why can't you write swap(int a, int b) that swaps the caller's variables? And why doesn't s = s.toUpperCase(); inside a method change the caller's String?
Think about it, then reveal the answer
Both parameters are copies: swapping or reassigning them leaves the caller alone. And Strings are immutable: toUpperCase() returns a new String, and assigning it only re-points the local copy. The fix in both cases: return the new value (or swap inside an array/object).
In real projects
"Is Java pass-by-reference?" is a favorite interview question. The answer: no, Java passes references by value. On the job, methods that mutate objects passed in (like removing items from a caller's list) cause spooky bugs, so many teams pass copies or return new objects instead.
Key takeaways
- Primitives: the method gets a copy of the value
- Objects: the method gets a copy of the reference
- Mutating the object through the copy is visible to the caller
- Reassigning the parameter is never visible to the caller
💡 Like handing someone a photocopy of your home address: they can visit and repaint your house, but scribbling a new address on their copy doesn't move your house.
"Is Java pass-by-reference or pass-by-value?" is one of the most famous questions on Stack Overflow. It was asked back in 2008, and people are still arguing in the comments.
Practice questions
What does this print?
static void change(StringBuilder sb) {
sb.append("!");
}
void main() {
var s = new StringBuilder("hi");
change(s);
System.out.println(s);
}- hi
- !
- hi!
- Compile error
Check your answer
hi!. sb and s are two references to the same StringBuilder. append changes that one object, so main sees hi!.
What does this print?
static void replace(StringBuilder sb) {
sb = new StringBuilder("bye");
sb.append("!");
}
void main() {
var s = new StringBuilder("hi");
replace(s);
System.out.println(s);
}- bye!
- hi
- hi!
- bye
Check your answer
hi. sb starts as a copy of s's reference, then is re-pointed to a new object. Only that new object gets the !, and s still refers to the original "hi".