Why this matters
- This one rule explains why a method sometimes appears to modify your data and sometimes appears to ignore your changes entirely.
- "Does Java pass by reference?" is asked constantly, and the confident wrong answer is the most common one.
- Once you hold this rule, defensive copying and immutability stop feeling like ceremony and start looking necessary.
The rule, applied twice
Java is always pass-by-value. There is no second mode. What differs is what the value is.
- For a primitive, the value is the number itself. The method gets its own copy, and changes to it are invisible outside.
- For an object, the value is the reference — the address of the object. The method gets its own copy of that address.
- Two copies of the same address point at one object, so mutating through either is visible to both.
- Reassigning the parameter only overwrites the method's private copy of the address. The caller's variable is untouched.
input.add(pen) is visible to the caller. input = new Cart() is not — it only repoints the copy.
Primitives: completely isolated
static void increment(int value) {
value = value + 1;
System.out.println("inside: " + value);
}
public static void main(String[] args) {
int count = 10;
increment(count);
System.out.println("outside: " + count);
}
inside: 11
outside: 10
The method received a copy of 10. Nothing it does to that copy can reach count.
Objects: mutation travels, reassignment does not
This is the pair of behaviours that confuses people, and seeing them side by side settles it.
static void mutate(StringBuilder text) {
text.append(" world"); // changes the shared object
}
static void reassign(StringBuilder text) {
text = new StringBuilder("replaced"); // changes only the local copy
text.append("!");
}
public static void main(String[] args) {
StringBuilder greeting = new StringBuilder("hello");
mutate(greeting);
System.out.println(greeting); // hello world
reassign(greeting);
System.out.println(greeting); // hello world — unchanged
}
hello world
hello world
Both methods received the same address. The first used it to reach the object and modify it. The second threw it away and pointed its own copy somewhere else, which the caller never learns about.
Why swap cannot work
The classic demonstration. There is no way to write a general swap in Java.
static void swap(Integer a, Integer b) {
Integer temp = a;
a = b;
b = temp;
}
public static void main(String[] args) {
Integer first = 1, second = 2;
swap(first, second);
System.out.println(first + ", " + second); // 1, 2 — nothing happened
}
The method swapped its own two local copies and then returned. Caller variables cannot be rebound from inside a method, full stop. If you need to exchange values, return them — in a record, an array, or by assigning at the call site.
Strings look like an exception but are not
static void change(String text) {
text = text.toUpperCase(); // creates a new String, rebinds the local copy
}
public static void main(String[] args) {
String name = "java";
change(name);
System.out.println(name); // java
}
String is immutable, so every operation returns a new object rather than modifying the
existing one. There is no mutation available to be visible, and the reassignment is local. Both
halves of the rule point the same way.
What this means for your API design
| Aspect | Parameter type | What a caller should expect |
|---|---|---|
| int, double, boolean | Completely safe | The method cannot affect the caller's value |
| String, Integer, LocalDate | Safe — immutable | No mutation is possible |
| List, Map, arrays | Mutable and shared | The method may add, remove or clear |
| Your own mutable class | Mutable and shared | Anything with a setter can be changed |
int, double, boolean
Parameter typeCompletely safeWhat a caller should expectThe method cannot affect the caller's valueString, Integer, LocalDate
Parameter typeSafe — immutableWhat a caller should expectNo mutation is possibleList, Map, arrays
Parameter typeMutable and sharedWhat a caller should expectThe method may add, remove or clearYour own mutable class
Parameter typeMutable and sharedWhat a caller should expectAnything with a setter can be changed
Immutability is what makes a parameter genuinely safe to hand over.
Common misreadings
- "Objects are passed by reference." The reference is passed by value. The distinction is the entire point, and the phrase "pass by reference value" captures it better.
- "Mutation proves pass-by-reference." True pass-by-reference would let a method rebind the caller's variable. Java never allows that.
- "
finalon a parameter changes the semantics." It only stops reassignment inside the method, which was already invisible outside. - "Strings behave differently." They behave identically. Immutability simply removes one of the two options.
Quick recall
Everything you need if you only revisit this box.
- Java is always pass-by-value. For objects, the value copied is the reference.
- A method can mutate the object you handed it, because both references point at the same object.
- A method cannot rebind your variable. Reassigning a parameter affects only the method's copy.
- Therefore
swapis impossible; return the new values instead. - Primitives and immutable objects are safe to pass; mutable collections and your own setter-bearing classes are not.
- Copy defensively when a method must not modify a collection it receives.
Test yourself
Answer these before moving on — recall is what makes it stick.