Every method call in Java hands something across. The question is what exactly gets handed over — and the answer is different depending on whether you are passing a primitive or an object. Misunderstanding that difference is the single most common source of confusion in introductory Java: beginners write a method that tries to swap two variables and wonder why nothing changed, or they hand an array to a helper and are surprised when the original comes back sorted. This lesson shows you precisely what Java copies, precisely when the caller's data can change, and why the shorthand phrase 'pass-by-reference' does more harm than good to your mental model.
flowchart LR
subgraph P["primitive int"]
A["x = 7"]
end
subgraph R["reference StringBuilder"]
B["s"] --> OBJ["StringBuilder<br/>hello"]
end
subgraph PC["pass-by-value copy"]
AC["copy = 7"]
BC["copy of s"] --> OBJ2["same object<br/>hello"]
end
A -->|"copies value"| AC
B -->|"copies reference"| BC
style OBJ fill:#b45309,color:#fff
style OBJ2 fill:#b45309,color:#fff
Primitives: the copy is the whole value
When you pass a primitive — an int, double, boolean, char, or any of their boxed cousins — Java copies the entire value into the parameter slot. The method now owns a completely independent variable that happens to hold the same number. Reassign that parameter and you are overwriting a local temporary; the caller's original variable lives in a different stack frame and is never consulted. This is why a method like addOne(x) cannot make x larger. The parameter is a photocopy, not the original document, and burning the photocopy does not scorch the original. Every primitive call is perfectly safe from side effects.
public class Main {
static void addOne(int n) {
n = n + 1;
System.out.println("inside: " + n);
}
public static void main(String[] args) {
int value = 5;
addOne(value);
System.out.println("outside: " + value);
}
}
References: the copy is the arrow, not the house
An object variable does not contain the object itself. It contains a reference — a memory address that points to where the object lives on the heap. When you pass that variable to a method, Java copies the reference by value. The method receives its own private copy of the arrow, but both arrows aim at the exact same house. That distinction is the heart of the matter: the parameter and the caller's variable are two separate references, yet they share one object. If the method mutates the object — appending to a StringBuilder, setting a field, sorting an array — that change is visible to the caller because both references aim at the single shared object in memory.
public class Main {
static void appendHello(StringBuilder sb) {
sb.append("hello");
System.out.println("inside: " + sb);
}
public static void main(String[] args) {
StringBuilder msg = new StringBuilder("hi ");
appendHello(msg);
System.out.println("outside: " + msg);
}
}
flowchart LR A["msg"] --> OBJ["StringBuilder<br/>hi hello"] B["sb = msg"] --> OBJ A -.->|"same object"| B style OBJ fill:#b45309,color:#fff
Mutation versus reassignment
Here is the subtlety that separates confident readers from stuck beginners. A method that mutates the shared object reaches the caller, because there is only one object and both references can see it. But a method that reassigns its parameter to a brand-new object merely repoints its own local arrow. The caller's arrow still aims at the original object, completely untouched. sb = new StringBuilder("new") inside a method changes only the local copy of the reference. The caller's msg variable never moves. This is why you cannot write a swap method in Java that exchanges two caller variables by reassigning parameters. The language simply does not provide any mechanism for a method to repoint the caller's variables.
public class Main {
static void reassign(StringBuilder sb) {
sb = new StringBuilder("new");
System.out.println("inside: " + sb);
}
public static void main(String[] args) {
StringBuilder msg = new StringBuilder("old");
reassign(msg);
System.out.println("outside: " + msg);
}
}
flowchart LR A["msg"] --> OBJ1["old"] B["sb"] --> OBJ1 B -.->|"reassign"| OBJ2["new"] style OBJ1 fill:#3776ab,color:#fff style OBJ2 fill:#b45309,color:#fff
Why 'pass-by-reference' is the wrong label
Some teachers call objects 'pass-by-reference' because mutations are visible. That shortcut causes real damage. True pass-by-reference means the method receives the caller's actual variable and can repoint it. Java never does that — it always copies the value. For object variables, that value happens to be a reference, so the copy aims at the same object. The accurate phrase is pass-by-value of the reference. Keeping the terminology straight protects you from writing code that assumes the caller's variable can be repointed, and from debugging sessions that last hours because you expected reference semantics that Java does not have. Precision in language leads to precision in code.
Primitive pass-by-value. A method doubles its parameter. Predict both lines.
public class Main {
static void doubleIt(int x) {
x = x * 2;
System.out.println("method: " + x);
}
public static void main(String[] args) {
int num = 4;
doubleIt(num);
System.out.println("main: " + num);
}
}
The parameter
xis a copy ofnum.Reassigning
xchanges only the local copy.The caller's
numstays at 4.
Reference mutation. A method changes an element of an array. Predict both lines — does the caller see the change?
public class Main {
static void zeroFirst(int[] nums) {
nums[0] = 0;
System.out.println("method: " + nums[0]);
}
public static void main(String[] args) {
int[] values = {7, 8, 9};
zeroFirst(values);
System.out.println("main: " + values[0]);
}
}
The parameter
numsis a copy of the referencevalues.Both references point at the same array object.
Mutating the array through
numsis visible throughvalues.
Reference reassignment. A method points its parameter at a new array. Predict both lines — does the caller change?
public class Main {
static void replace(int[] nums) {
nums = new int[]{1, 2, 3};
System.out.println("method: " + nums[0]);
}
public static void main(String[] args) {
int[] values = {7, 8, 9};
replace(values);
System.out.println("main: " + values[0]);
}
}
Reassigning
numsrepoints only the local parameter.The caller's
valuesstill aims at the original array.The original array still holds 7 at index 0.
A method receives an int[] array, sorts it with Arrays.sort(arr), and returns. What happens to the caller's array?
Arrays are objects, so the parameter gets a copy of the reference. Both the caller and the method aim at the same array object. Mutating that object — sorting it, changing elements — is visible to the caller.
Which statement accurately describes Java's parameter passing?
Java is strictly pass-by-value. For primitives the copy is the actual bits; for object variables the copy is the reference (the arrow), not the object itself. There is no pass-by-reference in Java.
Recap
- Java is pass-by-value for everything. Primitives receive a true copy of the value; references receive a copy of the reference.
- Two reference copies aim at the same object, so mutation through one is visible through the other.
- Reassigning a reference parameter repoints only the local copy; the caller's variable still aims at the original object.
- True pass-by-reference would let a method repoint the caller's variable — Java does not do this.
- The accurate description is pass-by-value of the reference, not 'pass-by-reference'.
Next you will learn how to protect your objects from accidental mutation by other methods — immutability and defensive copying.