Java Pass by Value vs Pass by Reference
The Great Java Debate: Pass by Value or Reference?
If you’ve spent any amount of time learning Java, you’ve probably come across the question that sparks endless confusion:
Is Java pass by value or pass by reference?
This debate has been going on for years, especially among beginners. Some say Java is pass by reference because objects seem to change inside methods. Others insist it’s pass by value because of how variables behave when passed around. The truth is a bit nuanced, and understanding it requires peeling back what those terms actually mean.
The good news? Once you really get what Java does under the hood, this entire topic becomes crystal clear.
Let’s start by setting the stage.
Why This Question Even Matters
When you write code in Java, you constantly pass data into methods — numbers, strings, arrays, objects, collections, and more. Whether you’re manipulating a user object, updating a list, or incrementing a number, understanding how Java passes these values determines how your code behaves.
If you misunderstand this concept, you might end up frustrated when your changes inside a method don’t reflect outside it.
For example, imagine you write a function to swap two integers — and it doesn’t work. Or you modify a field of an object inside a method and suddenly it affects your original object, even though you never returned anything. That’s when the confusion begins.
Knowing whether Java is pass by value or pass by reference helps you predict what your code will actually do, not just what you think it should do.
The Source of the Confusion
At the heart of the issue is how Java handles primitive types and objects differently.
When you pass a primitive (like an int, double, or boolean) to a method, it behaves one way.
When you pass an object (like a String, List, or custom Person class), it behaves in another — and that’s where people get tripped up.
Here’s what usually happens:
- You pass a primitive to a method, try to change it inside, and realize the original doesn’t change.
- Then you pass an object to a method, modify it inside, and see that the original does change.
At first glance, it looks like Java is using pass by reference for objects. But that’s not what’s really happening. Java still passes by value — it just happens that the value being passed for an object is a reference (a pointer to where the object lives in memory).
That subtle distinction is the key to understanding everything that follows.
Clearing Up the Terms Before We Dive In
Before we go deeper, it’s important to define what “pass by value” and “pass by reference” actually mean. These are not just buzzwords; they describe two different ways a programming language can send data into functions or methods.
Pass by Value
When you pass a variable by value, the method gets a copy of the variable’s value.
That means any changes inside the method affect only the copy, not the original.
Example:
If you pass x = 10 into a method, and the method changes it to 20, the original x outside the method is still 10. The method worked on a copy.
Pass by Reference
When you pass a variable by reference, the method receives a reference to the actual variable itself, not a copy.
So if the method changes it, the original variable changes too.
Example:
If you pass an object by reference and modify it inside the method, the changes reflect outside because both the caller and the method are referring to the same object in memory.
Java, as we’ll soon explore, doesn’t allow true pass by reference — but it passes copies of references, which makes it feel like it sometimes does.
What You’ll Learn in This Post
By the end of this article, you’ll have a rock-solid understanding of:
- What Java really means by “pass by value”
- Why objects seem to behave differently from primitives
- How memory plays a role in this behavior
- How to visualize what’s actually happening when you call a method
- And how to avoid common mistakes that even experienced developers make
So, the next time someone in a forum insists that “Java is pass by reference,” you’ll know exactly why that’s wrong — and you’ll be able to explain it clearly, with confidence and code examples to back it up.
Understanding How Java Handles Variables
Before we can understand how Java passes values to methods, we need to understand how Java treats variables themselves. At first glance, it seems straightforward — you declare a variable, assign a value, and use it. But under the hood, Java treats primitive types and reference types very differently. That difference lies at the heart of the “pass by value vs pass by reference” confusion.
Variables Are Just Containers for Values
In Java, a variable is like a small labeled box that stores some data. But depending on the type of data, what goes inside that box changes.
When you declare a primitive type variable like an int or a boolean, the box holds the actual value.
When you declare an object (or reference type), the box doesn’t hold the object itself — it holds a reference (think of it like an address) to where that object is stored in memory.
Here’s a simple example:
int a = 5;
String name = "Varun";
In this example:
- The variable
aliterally contains the value5. - The variable
namedoesn’t store the string “Varun” directly. Instead, it stores a reference to where the string object “Varun” is located in memory.
So, even though both a and name look like they’re storing data, they’re doing it in different ways.
Primitive Types vs Reference Types
Let’s take a closer look at how Java divides data into primitive and reference types.
Primitive Types
Java’s primitive types are the most basic kinds of data. They include:
byte,short,int,long– for integer numbersfloat,double– for decimal numberschar– for single charactersboolean– for true/false values
Each primitive type directly stores its value in the variable. When you assign or copy it, you’re copying the actual value, not a reference.
For example:
int x = 10;
int y = x;
y = 20;
System.out.println(x); // Output: 10
Here, changing y doesn’t affect x because y got its own copy of the value 10. The two boxes are completely independent.
Reference Types
Reference types include all objects — like instances of classes, arrays, lists, and even strings. When you declare a variable of a reference type, it doesn’t hold the object directly. It holds a reference to where the object lives on the heap.
Example:
Person p1 = new Person("Varun");
Person p2 = p1;
p2.name = "Rao";
System.out.println(p1.name); // Output: Rao
In this example, both p1 and p2 point to the same Person object. So when p2 changes the name field, it affects the same object that p1 points to. This makes it look like Java is using pass by reference — but what’s actually happening is that the reference value (the memory address) is being copied.
The Role of Memory: Stack and Heap
To fully understand this behavior, it helps to visualize how Java manages memory. The stack and heap are the two main areas involved in storing variables and objects.
Stack Memory
- The stack stores method calls, local variables, and references.
- Each time a method is called, a new “frame” is added to the stack.
- Primitive values and references (addresses pointing to objects) live here.
Heap Memory
- The heap is where Java stores all objects created with
new. - Objects are not stored in the stack; instead, the stack holds references (addresses) pointing to them in the heap.
Here’s how that looks conceptually:
Stack: Heap:
----------------- -----------------
a -> 5 (nothing)
name -> 0x123 "Varun" @ 0x123
p1 -> 0x456 Person{name="Rao"} @ 0x456
p2 -> 0x456 (same object)
Both p1 and p2 store the same reference value (0x456) that points to the same Person object in the heap. Changing that object through either reference affects the same data.
Assignments and Copies in Java
Every time you assign one variable to another in Java, one of two things happens depending on the type:
For Primitives
A copy of the value is made.
Changing one variable doesn’t affect the other.
For Objects
A copy of the reference is made.
Both variables still point to the same object, so changes to that object will be visible through either reference.
Example:
int a = 10;
int b = a;
b = 20; // Only b changes
Person p1 = new Person("Alice");
Person p2 = p1;
p2.name = "Bob"; // Both p1 and p2 now point to "Bob"
In short:
aandbare two different boxes holding independent values.p1andp2are two boxes holding the same address, pointing to one shared object.
Understanding This Is Key to Understanding Method Calls
Why does all this matter for pass by value vs pass by reference?
Because when you pass a variable into a method, Java copies whatever is inside the box.
- If it’s a primitive, it copies the value.
- If it’s a reference, it copies the address.
That distinction explains why objects can be modified inside methods but primitives cannot. It’s not because Java is pass by reference — it’s because what’s being passed by value happens to be a reference.
We’ll explore exactly how that works when we get to method calls, but for now, just remember this one simple rule:
In Java, variables hold either actual values (for primitives) or references to objects (for everything else).
When you pass them around, Java always copies what’s inside the variable — never the variable itself.
What Does “Pass by Value” Really Mean?
Before we dive into the specifics of how Java applies the concept of pass by value, it’s important to understand what this phrase actually means in programming. It’s one of those terms that sounds simple but tends to cause endless confusion when combined with how Java handles objects and memory.
At its core, pass by value is about what gets handed over when a method is called. Is it the actual variable? Or just a copy of it? In Java’s case, it’s always a copy of the value — never the variable itself. But the tricky part lies in understanding what exactly that “value” represents when dealing with objects.
The Core Idea Behind Pass by Value
In Java, when you pass a variable to a method, Java makes a copy of the variable’s current value and gives that copy to the method. That’s the heart of pass by value. The method works on its own copy, and whatever changes it makes do not affect the original variable in the caller’s scope.
Here’s a simple way to visualize it: imagine writing a phone number on a sticky note and giving a friend a photocopy of that note. They can write or erase anything on their copy, but your original note remains unchanged.
Let’s see this in action with a simple example.
Example: Pass by Value with a Primitive
public class PassByValueExample {
public static void main(String[] args) {
int number = 10;
modifyNumber(number);
System.out.println("After method call: " + number);
}
public static void modifyNumber(int n) {
n = 20;
System.out.println("Inside method: " + n);
}
}
Output:
Inside method: 20
After method call: 10
What happened here?
When we call modifyNumber(number), Java copies the value of number (which is 10) into a new variable n inside the method. When the method changes n to 20, it’s only modifying its local copy. The original variable number in main() is untouched because Java passed the value, not the actual variable.
This is pure pass by value behavior.
Why It’s Easy to Misunderstand
Things start to get confusing when you move from primitives to objects. The word “value” doesn’t always mean the same kind of thing.
For primitives, the value is the actual number or boolean stored in the variable.
For objects, the value is the reference, not the object itself. That means when Java copies the “value” of a reference variable, it’s actually copying the address pointing to the object — not the object itself.
So when you pass an object to a method, you’re still passing by value, but that value happens to be a reference.
Example: Pass by Value with an Object Reference
class Person {
String name;
Person(String name) {
this.name = name;
}
}
public class ObjectExample {
public static void main(String[] args) {
Person p = new Person("Varun");
changeName(p);
System.out.println("After method call: " + p.name);
}
public static void changeName(Person person) {
person.name = "Rao";
System.out.println("Inside method: " + person.name);
}
}
Output:
Inside method: Rao
After method call: Rao
At first glance, this looks like Java is using pass by reference — after all, the method changed the original object. But here’s what’s really happening:
- The variable
pinmain()holds a reference to thePersonobject in memory (say, at address0x123). - When you call
changeName(p), Java copies the value of that reference (the address0x123) into the method parameterperson. - Both
pandpersonnow point to the same memory location. - When you change the object’s field (
person.name), both see the change because they refer to the same object in memory.
The key insight: Java still passed a copy of the reference, not the reference itself. The method cannot change which object p points to; it can only modify the contents of the object at that location.
The Limits of Pass by Value in Java
Understanding what pass by value implies also helps you see what it cannot do. Since Java passes a copy of the value (even if that value is a reference), you cannot make a method reassign the caller’s variable to point to a different object.
Example: Reassigning a Reference Inside a Method
class Person {
String name;
Person(String name) {
this.name = name;
}
}
public class ReassignExample {
public static void main(String[] args) {
Person p = new Person("Alice");
reassign(p);
System.out.println("After method call: " + p.name);
}
public static void reassign(Person person) {
person = new Person("Bob");
System.out.println("Inside method: " + person.name);
}
}
Output:
Inside method: Bob
After method call: Alice
This time, the original Person object remains unchanged. Why? Because the method only changed its local copy of the reference to point to a new object. The original reference p in the main() method still points to the original Person("Alice") object.
This perfectly demonstrates how Java’s pass-by-value rule holds true — even for objects.
Pass by Value in Other Languages vs Java
In some programming languages, you can truly pass by reference, meaning a function can directly modify the caller’s variable. C++ allows this using the ampersand symbol (&), and Python uses reference semantics by default for mutable types.
Java deliberately avoids this complexity. By always passing by value — even if that value is a reference — it ensures that a method cannot alter which object the caller’s variable points to. It can modify the contents of that object but not the binding of the variable itself.
This design makes Java’s behavior more predictable and less error-prone, even though it can initially seem confusing.
The Key Takeaway So Far
Every time you call a method in Java, think of it like this:
Java makes a copy of whatever is inside the variable’s “box.”
- If the box contains a primitive value, the method gets a copy of that number.
- If the box contains a reference, the method gets a copy of that address.
Once you understand that principle, everything about Java’s method parameter behavior — from modifying integers to changing object fields — falls neatly into place.
The Common Misconception: Java Is Pass by Reference
If you’ve ever browsed a programming forum or talked to other developers about how Java handles method parameters, you’ve almost certainly encountered the claim that “Java is pass by reference.” It’s a common statement, and at first glance, it even seems true — especially when you see how objects behave when passed to methods.
But this belief comes from a misunderstanding of what Java actually does under the hood. Java does something subtler: it passes a copy of the reference, not the reference itself. Once you understand this, the whole “pass by reference” illusion disappears.
Let’s break down why so many developers get this wrong, what pass by reference truly means, and how Java’s design intentionally avoids it.
Why Developers Think Java Is Pass by Reference
The confusion usually begins when people notice how object fields behave inside methods. Consider this example:
class Car {
String color;
Car(String color) {
this.color = color;
}
}
public class Example {
public static void main(String[] args) {
Car car = new Car("Red");
paintCar(car);
System.out.println("After method call: " + car.color);
}
public static void paintCar(Car c) {
c.color = "Blue";
System.out.println("Inside method: " + c.color);
}
}
Output:
Inside method: Blue
After method call: Blue
At first glance, this seems like pass by reference. You passed car into the paintCar method, changed its color inside the method, and the change persisted outside the method. It feels like the method had direct access to the original object.
But what’s really happening is that Java passed a copy of the reference — that is, a copy of the memory address pointing to the same object. Both the original variable (car) and the method parameter (c) hold references to the same object in memory. So, modifying that object through either variable produces the same result.
What Pass by Reference Would Actually Look Like
To understand why this is different from true pass by reference, let’s first define what that means in the strict programming sense.
Pass by Reference Definition
In a true pass-by-reference language, the method receives direct access to the caller’s variable — not just a copy of its value. This means the method can reassign the caller’s variable itself, changing what it points to or even altering its value completely.
Languages like C++ and C# (with the ref keyword) support this behavior.
Example in a Pass-by-Reference World
Imagine if Java actually supported pass by reference. Then, the following code would work as people often expect:
public class SwapExample {
public static void main(String[] args) {
int a = 5;
int b = 10;
swap(a, b);
System.out.println("a: " + a + ", b: " + b);
}
public static void swap(int x, int y) {
int temp = x;
x = y;
y = temp;
}
}
If Java were pass by reference, this program would print:
a: 10, b: 5
But instead, the actual output is:
a: 5, b: 10
That’s because Java doesn’t give the swap method access to the original variables a and b. It only gives copies of their values. Even if you tried this with objects, the same logic applies — the reference itself is copied.
Demonstrating the Difference
To see how this plays out with objects, let’s look at two similar but subtly different examples.
Example 1: Modifying the Object’s Internal State
class Box {
int value;
Box(int value) {
this.value = value;
}
}
public class Demo {
public static void main(String[] args) {
Box box = new Box(10);
modifyBox(box);
System.out.println("After method call: " + box.value);
}
public static void modifyBox(Box b) {
b.value = 50;
System.out.println("Inside method: " + b.value);
}
}
Output:
Inside method: 50
After method call: 50
Here, the change sticks. The method modifies the same object that main() has a reference to. But this still isn’t pass by reference — both variables (box and b) simply hold copies of the same reference value.
Example 2: Reassigning the Reference
Now, look at this version:
class Box {
int value;
Box(int value) {
this.value = value;
}
}
public class Demo {
public static void main(String[] args) {
Box box = new Box(10);
reassignBox(box);
System.out.println("After method call: " + box.value);
}
public static void reassignBox(Box b) {
b = new Box(99);
System.out.println("Inside method: " + b.value);
}
}
Output:
Inside method: 99
After method call: 10
If Java were truly pass by reference, the method’s reassignment would change what box points to in the caller’s scope. But that doesn’t happen. box still points to the original object (value = 10). The method only changed its local copy of the reference to point somewhere else.
This proves that Java never passes variables by reference — only copies of their values, which can happen to be references.
Why Java Chose Pass by Value for Everything
Java’s creators intentionally designed the language this way for safety and simplicity. Early programming languages like C allowed pointers and references to be freely manipulated, which often led to bugs and crashes.
By enforcing pass by value, Java ensures that:
- Methods can never directly modify local variables from another scope.
- Variable ownership remains clear — each scope controls its own variables.
- You can safely pass objects without worrying about accidental reference reassignments.
In other words, Java passes by value for primitives and by value of reference for objects. This small design choice prevents a huge class of programming errors and keeps Java’s behavior consistent and predictable.
A Mental Model to Avoid Confusion
A helpful way to remember this is to think of Java variables as name tags attached to memory boxes.
- For primitives, the name tag is attached directly to the box that holds the value.
- For objects, the name tag points to another box that holds the actual object.
When you pass a variable into a method, Java doesn’t hand over the name tag — it photocopies it.
If that photocopy points to an object, both copies refer to the same object, but you can’t change where the original name tag points.
This simple mental image clears up the confusion: Java copies the tag, not the box itself. That’s why modifying an object works but reassigning it doesn’t.
Pass by Value with Primitives
Now that we’ve cleared up the confusion around what “pass by value” means, it’s time to see how it behaves in practice — starting with the simplest case: primitive data types.
In Java, primitives are the foundation of data storage. They hold their values directly, not through references. This makes them a perfect way to observe Java’s pass-by-value behavior because the results are simple and predictable.
How Java Treats Primitive Values
When you pass a primitive variable to a method, Java creates a copy of its value and gives that copy to the method. The method can do whatever it wants with its copy, but the original variable remains completely unchanged.
This rule applies to all eight primitive types in Java:byte, short, int, long, float, double, char, and boolean.
Each time one of these is passed as an argument to a method, a new copy of its value is created on the method’s call stack.
Let’s illustrate this with an example.
Example 1: Passing an int
public class PrimitiveExample {
public static void main(String[] args) {
int number = 10;
changeValue(number);
System.out.println("After method call: " + number);
}
public static void changeValue(int num) {
num = 20;
System.out.println("Inside method: " + num);
}
}
Output:
Inside method: 20
After method call: 10
At first, it looks like the changeValue method changes the variable — but it doesn’t. Here’s what’s actually happening behind the scenes:
- In
main(), the variablenumberis stored on the stack with the value10. - When you call
changeValue(number), Java creates a copy of the value10and assigns it to the parameternum. - Inside the method,
numis now a separate variable that happens to start with the same value asnumber. - When
numchanges to20, the change affects only that copy. - When the method finishes, the copy is discarded, and
numberremains unchanged.
This simple example perfectly demonstrates Java’s “pass by value” rule in action.
Visualizing What Happens in Memory
To really understand this, let’s visualize what’s happening in memory.
Before the method call:
main stack frame:
number -> 10
During the method call:
main stack frame:
number -> 10
changeValue stack frame:
num -> 10 (copy of number)
When you assign num = 20, only the copy changes:
changeValue stack frame:
num -> 20
The original number variable in main() is unaffected. Once the method finishes executing, its stack frame is removed, and the local copy num disappears.
This is why the value printed in main() remains 10.
Example 2: Using Different Primitive Types
The same principle holds for all primitive types — not just int.
public class PrimitiveTypesExample {
public static void main(String[] args) {
double price = 99.99;
boolean active = true;
modify(price, active);
System.out.println("After method call: price = " + price + ", active = " + active);
}
public static void modify(double cost, boolean flag) {
cost = 49.99;
flag = false;
System.out.println("Inside method: cost = " + cost + ", flag = " + flag);
}
}
Output:
Inside method: cost = 49.99, flag = false
After method call: price = 99.99, active = true
Once again, both variables remain unchanged outside the method. Java passed copies of their values, not the variables themselves.
Example 3: Trying to Modify a Primitive via Method
Beginners often try to “return” a changed primitive through a method and get surprised when it doesn’t seem to work.
For example:
public class IncrementExample {
public static void main(String[] args) {
int score = 50;
increment(score);
System.out.println("Final score: " + score);
}
public static void increment(int s) {
s++;
System.out.println("Inside method: " + s);
}
}
Output:
Inside method: 51
Final score: 50
The variable s inside the increment method is just a temporary copy of score. When s increases, it doesn’t affect score. The method’s changes vanish when it finishes execution.
If you wanted to actually change the caller’s value, you’d have to return the new value and explicitly reassign it:
public class IncrementExample {
public static void main(String[] args) {
int score = 50;
score = increment(score);
System.out.println("Final score: " + score);
}
public static int increment(int s) {
s++;
return s;
}
}
Output:
Final score: 51
By explicitly returning and reassigning, you’re copying the result of the method back into the original variable — not modifying it directly.
Why Java Does It This Way
Java’s decision to pass primitives by value ensures that methods cannot accidentally alter the caller’s data. This design choice makes code easier to reason about. You always know that when you pass a primitive into a method, it’s safe — the method cannot change your original variable unless you explicitly use the return value.
This predictability is one of the reasons Java is considered a robust and beginner-friendly language.
Key Takeaway from Primitive Examples
Every primitive you pass into a method in Java behaves like a snapshot. The method gets a picture of the value at the time of the call, not the original variable itself.
Even if the method changes its local copy, your original variable is completely unaffected once the method finishes.
So, when you see a primitive not changing after a method call, it’s not a bug or a special rule — it’s simply Java doing exactly what it’s supposed to do: passing by value.
Pass by Value with Objects
Now that we’ve seen how Java handles primitives, it’s time to look at how it behaves with objects. This is where things get interesting — and where most of the confusion around “pass by value vs pass by reference” comes from.
At first glance, it seems like Java treats objects differently. When you modify an object inside a method, the change seems to persist outside the method. That leads many developers to believe Java must be passing objects by reference. But in reality, Java is still passing by value — it’s just that the value being copied is the reference to the object, not the object itself.
Let’s unpack that carefully with examples and visual explanations.
How Java Passes Object References
When you declare an object variable in Java, that variable doesn’t hold the actual object. Instead, it holds a reference — a sort of pointer or address that tells Java where to find that object in memory.
When you pass an object to a method, Java makes a copy of the reference, not the object. Both the original and the method parameter now point to the same object in memory.
That’s why, if the method modifies the contents of the object, you see the changes outside. The two references still lead to the same memory location.
However, if the method reassigns its local reference to point to a different object, that reassignment doesn’t affect the original variable.
Example 1: Modifying the Contents of an Object
Let’s start with a simple example where a method changes an object’s field.
class Student {
String name;
Student(String name) {
this.name = name;
}
}
public class ObjectExample {
public static void main(String[] args) {
Student s1 = new Student("Varun");
changeName(s1);
System.out.println("After method call: " + s1.name);
}
public static void changeName(Student s) {
s.name = "Rao";
System.out.println("Inside method: " + s.name);
}
}
Output:
Inside method: Rao
After method call: Rao
Here’s what happens step by step:
s1holds a reference to aStudentobject in memory, say at address0x123.- When
changeName(s1)is called, Java copies that reference value (0x123) into the method parameters. - Now, both
s1andspoint to the sameStudentobject in the heap. - When the method changes
s.name, it changes the field of that same object. - When the method ends, the local variable
sis destroyed, but the object remains. Thes1reference still points to it, so the change is visible.
Java still followed pass-by-value rules — it passed a copy of the reference. Both references point to the same object, so changes to that object are reflected everywhere.
Example 2: Reassigning the Reference Inside a Method
Now, let’s look at what happens if you reassign the reference itself inside the method.
class Student {
String name;
Student(String name) {
this.name = name;
}
}
public class ReassignExample {
public static void main(String[] args) {
Student s1 = new Student("Varun");
reassignStudent(s1);
System.out.println("After method call: " + s1.name);
}
public static void reassignStudent(Student s) {
s = new Student("Rao");
System.out.println("Inside method: " + s.name);
}
}
Output:
Inside method: Rao
After method call: Varun
This example makes it clear that the method cannot change what the original variable points to.
Here’s why:
s1inmain()points to the originalStudentobject at address0x123.- When you call
reassignStudent(s1), Java passes a copy of that reference (0x123) into the method parameters. - Inside the method,
sis reassigned to a newStudentobject — say, at address0x456. - The change only affects the local copy of the reference, not
s1. - When the method ends, the local variable
sdisappears, ands1still points to the original object.
This behavior proves that Java doesn’t pass references by reference — it passes a copy of the reference by value.
Visualizing Object Passing in Memory
Let’s visualize what’s happening in both examples to understand the difference.
Example 1 (Modifying Object Fields)
main() stack frame:
s1 → 0x123
changeName() stack frame:
s → 0x123
heap:
0x123 → Student{name="Rao"}
Both variables s1 and s point to the same object in the heap, so changes made inside the method affect the same object.
Example 2 (Reassigning Reference)
main() stack frame:
s1 → 0x123
reassignStudent() stack frame:
s → 0x123 → (then reassigned to 0x456)
heap:
0x123 → Student{name="Varun"}
0x456 → Student{name="Rao"}
After the reassignment, the method’s local variable s points to a completely new object, while s1 still points to the original one.
When the method finishes, the local variable disappears, but the original object and reference remain intact.
Example 3: Changing an Object’s Internal Structure
This same principle applies not just to fields, but also to any internal state, like elements in an array or objects within a collection.
import java.util.ArrayList;
public class ListExample {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Apple");
modifyList(list);
System.out.println("After method call: " + list);
}
public static void modifyList(ArrayList<String> l) {
l.add("Banana");
System.out.println("Inside method: " + l);
}
}
Output:
Inside method: [Apple, Banana]
After method call: [Apple, Banana]
Here’s why the change persisted:
- The method
modifyListreceived a copy of the reference to the sameArrayListobject. - Adding a new element modifies the object itself, not the reference.
- Both the method and the caller refer to the same list in memory.
Now, if we tried to reassign the reference inside the method, it wouldn’t affect the original list:
public static void modifyList(ArrayList<String> l) {
l = new ArrayList<>();
l.add("Orange");
System.out.println("Inside method: " + l);
}
Output:
Inside method: [Orange]
After method call: [Apple]
Once again, the method created a new object and changed its local reference. The original list in main() remained untouched.
Key Insight: Two Levels of Change
When you pass an object to a method in Java, there are two distinct levels of possible modification:
- Modifying the object’s contents — This change persists because both references point to the same object.
- Reassigning the reference itself — This change does not persist because the reference is copied, and the reassignment affects only the local variable inside the method.
That’s the crucial difference that explains nearly every “why didn’t my object change?” or “why did it change?” question you’ll encounter when working with Java methods.
The key is always to ask: am I modifying the object’s contents, or am I reassigning the reference itself?
Visualizing What Happens in Memory
When people talk about Java’s behavior in terms of “pass by value” and “pass by reference,” the confusion often comes from not visualizing what’s happening in memory. To truly understand why Java always passes by value, you need to see how the stack and heap work together when variables and methods interact.
Everything that happens when you call a method — from variable creation to object modification — can be explained by understanding these two memory regions.
The Two Main Memory Areas in Java
Java uses two key areas of memory to store data while your program runs:
- Stack memory – used for storing method calls, local variables, and references.
- Heap memory – used for storing objects created with the
newkeyword.
Together, they form the backbone of how Java executes your code.
Let’s break down what happens in each of these.
Stack Memory
- The stack is where Java keeps track of method calls and local variables.
- Each time a method is called, a new stack frame is created.
- This frame contains all the variables declared inside that method — both primitives and object references.
- When the method finishes, its frame is removed (popped) from the stack.
Since the stack is temporary, anything stored there (like method parameters) disappears once the method exits.
Heap Memory
- The heap is where Java stores all objects created using
new. - Objects live in the heap until there are no more references to them — at which point the garbage collector cleans them up.
- Variables on the stack don’t contain the objects themselves — they contain references, or pointers, that tell Java where those objects are in the heap.
In simpler terms:
- The stack holds variable names and references.
- The heap holds the actual data (the objects themselves).
Example: Stack and Heap in Action
Consider this simple example:
class Book {
String title;
Book(String title) {
this.title = title;
}
}
public class MemoryExample {
public static void main(String[] args) {
Book b1 = new Book("Java Basics");
changeTitle(b1);
System.out.println("After method call: " + b1.title);
}
public static void changeTitle(Book b) {
b.title = "Advanced Java";
System.out.println("Inside method: " + b.title);
}
}
Output:
Inside method: Advanced Java
After method call: Advanced Java
At first glance, this seems like “pass by reference” — the method changes the object, and the change persists. But here’s what’s really happening in memory.
Before the method call
Stack (main):
b1 → 0x001
Heap:
0x001 → Book { title = "Java Basics" }
The variable b1 lives on the stack, but it doesn’t hold the Book object directly. It holds the reference (an address, here shown as 0x001) to where the object lives in the heap.
During the method call
When changeTitle(b1) is called, Java creates a copy of the reference.
Stack (main):
b1 → 0x001
Stack (changeTitle):
b → 0x001
Heap:
0x001 → Book { title = "Java Basics" }
Both b1 and b point to the same memory address in the heap. When the method changes b.title, it’s changing the object at address 0x001. Since both references point to that same object, the change is visible outside the method.
When the method finishes, the b reference is removed from the stack, but the object and b1 still exist.
After the method call
Stack (main):
b1 → 0x001
Heap:
0x001 → Book { title = "Advanced Java" }
The Book object in the heap remains modified because there was only one copy of it — only the reference was duplicated, not the object itself.
Example: Reassignment and a New Object
Let’s modify the example to show what happens when we reassign the object inside the method.
class Book {
String title;
Book(String title) {
this.title = title;
}
}
public class MemoryExample {
public static void main(String[] args) {
Book b1 = new Book("Java Basics");
reassignBook(b1);
System.out.println("After method call: " + b1.title);
}
public static void reassignBook(Book b) {
b = new Book("Python Essentials");
System.out.println("Inside method: " + b.title);
}
}
Output:
Inside method: Python Essentials
After method call: Java Basics
Now, the change does not persist. Let’s look at why by visualizing memory again.
Before the method call
Stack (main):
b1 → 0x001
Heap:
0x001 → Book { title = "Java Basics" }
During the method call
When reassignBook(b1) is called, Java copies the reference.
Stack (main):
b1 → 0x001
Stack (reassignBook):
b → 0x001
Heap:
0x001 → Book { title = "Java Basics" }
Then, the method executes this line:
b = new Book("Python Essentials");
Now, the local variable b points to a new object in memory.
Stack (main):
b1 → 0x001
Stack (reassignBook):
b → 0x002
Heap:
0x001 → Book { title = "Java Basics" }
0x002 → Book { title = "Python Essentials" }
The key moment: b now points to a completely new Book object (0x002), but b1 in the main() method still points to the original object (0x001).
When the method ends, its stack frame disappears, and the local variable b (and its reference to 0x002) is destroyed. The original b1 variable and object remain unaffected.
This perfectly illustrates how Java’s pass-by-value mechanism works even with objects.
The Copy of the Reference Concept
To summarize this behavior in memory terms:
- When you pass a primitive, Java copies the actual value.
- When you pass an object, Java copies the reference (address).
That’s why you can change the object’s internal data but can’t make the caller’s reference point to a new object.
It’s like giving someone a photocopy of a map with your house marked on it. They can scribble on the map or even walk to your house and paint it a new color, but they can’t make your original map point to a new location — they only have a copy of it.
The Role of Stack Frames in Method Calls
Every time a method is called, Java creates a new stack frame. That frame holds:
- Local variables
- Parameters (which are copies of arguments)
- Temporary computation data
When the method returns, that frame is discarded, and all its variables vanish. The only thing that persists is the data on the heap — the objects themselves — as long as there’s at least one live reference to them somewhere else.
This is why changes to an object persist after a method call (the heap object remains the same), but reassigning a reference doesn’t (the local stack variable disappears).
Why This Model Makes Java Predictable
Once you internalize this memory model, Java’s behavior becomes easy to reason about. There’s no mystery to why sometimes changes stick and sometimes they don’t. The difference comes down to where the modification occurs — inside the heap (the object) or inside the stack (the reference variable).
Understanding the stack and heap relationship is the foundation for mastering not only method parameter behavior but also topics like garbage collection, memory leaks, and object lifetimes in Java.
Pass by Value in Arrays and Collections
Arrays and collections in Java behave like objects, which means they follow the same pass-by-value principle — Java passes a copy of the reference to the array or collection, not the array or collection itself. This often leads to confusion because when you modify an array or collection inside a method, the changes seem to persist outside of it.
But as we’ve seen before, this isn’t pass by reference — it’s just pass by value where the value being passed happens to be a reference to a shared object.
Let’s look at exactly how this works with detailed examples.
Arrays Are Objects in Java
Before diving into examples, it’s important to remember that arrays are objects in Java. When you create an array, you’re not creating a simple variable — you’re creating an object on the heap, and your array variable is a reference to that object.
For example:
int[] numbers = {1, 2, 3};
Here, numbers doesn’t hold the actual elements 1, 2, 3. Instead, it holds a reference (like a pointer) to the memory location where those values are stored in the heap.
When you pass this array into a method, Java copies the reference — not the array. Both the caller and the method parameter refer to the same array object in memory.
Example 1: Modifying an Array Inside a Method
public class ArrayExample {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
modifyArray(arr);
System.out.println("After method call: " + java.util.Arrays.toString(arr));
}
public static void modifyArray(int[] a) {
a[0] = 99;
System.out.println("Inside method: " + java.util.Arrays.toString(a));
}
}
Output:
Inside method: [99, 2, 3]
After method call: [99, 2, 3]
Even though we changed the array inside the method, the change persisted outside it.
Here’s what’s happening under the hood:
- The variable
arrinmain()holds a reference to the array object (say, address0x100). - When
modifyArray(arr)is called, Java copies that reference (0x100) into the parametera. - Both
arrandanow point to the same array object in the heap. - When the method changes
a[0], it modifies the same array object thatarrpoints to.
No new array is created, and the change is reflected everywhere because both variables share the same object reference.
Example 2: Reassigning the Array Inside the Method
Now let’s see what happens if we reassign the array reference inside the method.
public class ArrayReassign {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
reassignArray(arr);
System.out.println("After method call: " + java.util.Arrays.toString(arr));
}
public static void reassignArray(int[] a) {
a = new int[] {4, 5, 6};
System.out.println("Inside method: " + java.util.Arrays.toString(a));
}
}
Output:
Inside method: [4, 5, 6]
After method call: [1, 2, 3]
This time, the original array is unaffected. Why?
When we assigned a = new int[] {4, 5, 6}, the local variable a stopped pointing to the original array and began pointing to a new one. But this reassignment only affected the local copy of the reference — the one inside the reassignArray method.
The original reference arr in the main() method still points to the original {1, 2, 3} array in the heap.
Once the method finishes, the local variable a disappears, and the new array {4, 5, 6} becomes eligible for garbage collection if nothing else refers to it.
So, even though arrays are mutable, Java still behaves consistently: it passes the reference by value.
Visualizing What Happens with Arrays
Let’s visualize the memory state during both examples.
Example 1 (Modification)
main() stack frame:
arr → 0x100
modifyArray() stack frame:
a → 0x100
heap:
0x100 → [1, 2, 3] (then modified to [99, 2, 3])
Both variables arr and a refer to the same array object, so the modification is visible outside the method.
Example 2 (Reassignment)
main() stack frame:
arr → 0x100
reassignArray() stack frame:
a → 0x100 → (then reassigned to 0x200)
heap:
0x100 → [1, 2, 3]
0x200 → [4, 5, 6]
After the reassignment, the local a points to a new array (0x200), while arr in main() continues to point to the original one (0x100).
This memory model explains why changes to the contents of arrays persist, but reassignments do not.
Collections Work the Same Way
Just like arrays, collections in Java — such as ArrayList, HashSet, or HashMap — are objects. When you pass a collection to a method, Java copies the reference to it. That means the method can modify the contents of the collection, but if it reassigns the reference, the change won’t affect the caller.
Example 1: Modifying a Collection
import java.util.*;
public class CollectionExample {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
addFruit(fruits);
System.out.println("After method call: " + fruits);
}
public static void addFruit(List<String> list) {
list.add("Mango");
System.out.println("Inside method: " + list);
}
}
Output:
Inside method: [Apple, Banana, Mango]
After method call: [Apple, Banana, Mango]
Because both fruits and list refer to the same ArrayList object, adding an element inside the method modifies the shared list.
Example 2: Reassigning a Collection
import java.util.*;
public class ReassignCollection {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
reassignList(fruits);
System.out.println("After method call: " + fruits);
}
public static void reassignList(List<String> list) {
list = new ArrayList<>();
list.add("Orange");
System.out.println("Inside method: " + list);
}
}
Output:
Inside method: [Orange]
After method call: [Apple]
Here, the reassignment doesn’t affect the caller’s list because the reference itself was copied. The local variable list points to a new ArrayList, but the original fruits list in main() remains unchanged.
Arrays and Collections Share the Same Rules
In both arrays and collections, you can think of two different types of operations:
- Mutating the object’s contents – changes persist outside the method because both variables refer to the same object.
- Reassigning the reference – changes do not persist because the local copy of the reference is modified, not the original variable.
This consistent behavior is what makes Java’s memory model elegant and predictable.
Once you understand that arrays and collections are just objects with different data structures underneath, it becomes easy to reason about how passing them to methods will behave.
Common Interview Traps and Examples
The concept of “pass by value vs pass by reference” often shows up in Java interviews because it tests your understanding of both language fundamentals and memory behavior. Many candidates get tripped up, not because the topic is hard, but because Java’s behavior looks like pass by reference when it isn’t.
In this section, we’ll explore some of the most common interview questions, tricky examples, and mental traps interviewers use to test your understanding — and how to reason through them correctly.
Why Interviewers Love This Question
From a recruiter’s point of view, this topic checks multiple skills at once:
- Understanding of how memory works in Java.
- Ability to explain tricky code behavior clearly.
- Knowledge of the difference between primitives and reference types.
- Comfort with debugging subtle issues in parameter passing.
Many experienced developers stumble here because they memorize that “Java is pass by value” but can’t explain what that means in practice. Interviewers often test this through code snippets that seem to contradict that rule.
Let’s go through those classic traps one by one.
Trap 1: The Swap Function
This is one of the oldest and most misleading examples.
public class SwapTest {
public static void main(String[] args) {
int a = 10;
int b = 20;
swap(a, b);
System.out.println("a = " + a + ", b = " + b);
}
public static void swap(int x, int y) {
int temp = x;
x = y;
y = temp;
}
}
Output:
a = 10, b = 20
At first glance, it looks wrong — the method clearly swaps the variables, so why doesn’t the result show that?
Here’s what’s really happening:
- Java passes copies of
aandbto the method. - Inside the method,
xandyare local copies that have no connection to the originals. - The swap works inside the method but is lost as soon as it returns.
This is a perfect example of how Java’s pass-by-value mechanism ensures that primitives remain isolated between methods.
If you wanted this to work, you’d have to return both values or wrap them in an object.
Trap 2: Swapping Object References
Candidates often think that using objects will make the swap work — and that’s where they fall into the next trap.
class Box {
int value;
Box(int value) {
this.value = value;
}
}
public class ObjectSwap {
public static void main(String[] args) {
Box b1 = new Box(10);
Box b2 = new Box(20);
swapBoxes(b1, b2);
System.out.println("b1.value = " + b1.value + ", b2.value = " + b2.value);
}
public static void swapBoxes(Box x, Box y) {
Box temp = x;
x = y;
y = temp;
}
}
Output:
b1.value = 10, b2.value = 20
Even though these are objects, the result still doesn’t change. Why?
Because Java passes copies of the references — the method swaps the copies, not the actual references in the caller. The original variables b1 and b2 in main() remain untouched.
However, if we modified the contents of the objects instead of reassigning the references, the change would persist.
public static void swapValues(Box x, Box y) {
int temp = x.value;
x.value = y.value;
y.value = temp;
}
Output:
b1.value = 20, b2.value = 10
The key takeaway: modifying the object’s state works, but swapping the references doesn’t.
Trap 3: Strings Are Immutable
Strings often cause confusion in interviews because they behave differently from other objects.
public class StringTest {
public static void main(String[] args) {
String s = "hello";
modifyString(s);
System.out.println("After method call: " + s);
}
public static void modifyString(String str) {
str = "world";
System.out.println("Inside method: " + str);
}
}
Output:
Inside method: world
After method call: hello
Even though String is an object, Java’s immutability rule prevents any method from changing its contents. When you reassign str = "world", you’re making the parameter point to a new string object, not modifying the original one.
Strings are effectively pass-by-value of reference just like other objects, but since they can’t be modified, it feels like they behave like primitives.
Trap 4: Arrays and Shared References
Arrays behave like mutable objects, which makes them a favorite tool for tricky interview questions.
public class ArrayTrap {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
modify(arr);
System.out.println("After method call: " + java.util.Arrays.toString(arr));
}
public static void modify(int[] nums) {
nums[0] = 99;
nums = new int[] {7, 8, 9};
System.out.println("Inside method: " + java.util.Arrays.toString(nums));
}
}
Output:
Inside method: [7, 8, 9]
After method call: [99, 2, 3]
The modification of the first element persists, but the reassignment doesn’t.
Explanation:
- When
nums[0] = 99, the method modifies the array that botharrandnumsreference. - When
nums = new int[]{7, 8, 9}, the method pointsnumsto a new array — butarrstill points to the old one.
Interviewers use this to see whether you understand the difference between mutating an object and reassigning a reference.
Trap 5: Collections and Shared State
This is another classic test of how well you understand references and mutability.
import java.util.*;
public class ListTrap {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
modifyList(names);
System.out.println("After method call: " + names);
}
public static void modifyList(List<String> list) {
list.add("Charlie");
list = new ArrayList<>();
list.add("David");
System.out.println("Inside method: " + list);
}
}
Output:
Inside method: [David]
After method call: [Alice, Bob, Charlie]
At first glance, this can look inconsistent — why did adding “Charlie” persist but “David” didn’t?
The answer is simple once you remember how Java handles references:
- The
add("Charlie")operation modifies the sharedArrayListobject that both variables reference. - The reassignment
list = new ArrayList<>()only changes the local copy of the reference, not the originalnamesvariable inmain().
Trap 6: Wrapper Classes and Autoboxing
Wrapper classes like Integer, Double, and Boolean behave like objects but are immutable, similar to String.
public class WrapperTrap {
public static void main(String[] args) {
Integer x = 5;
changeValue(x);
System.out.println("After method call: " + x);
}
public static void changeValue(Integer val) {
val = 10;
System.out.println("Inside method: " + val);
}
}
Output:
Inside method: 10
After method call: 5
Even though Integer is an object, it’s immutable, so reassignment doesn’t affect the original variable. This often surprises developers who assume wrapper types behave like primitives.
To actually change the value, you’d need to return a new Integer and assign it to the variable again.
Trap 7: Passing Objects to Modify Nested Objects
A slightly more advanced trick involves nested objects — for example, an object containing another object as a field.
class Engine {
int power;
Engine(int power) {
this.power = power;
}
}
class Car {
Engine engine;
Car(int power) {
this.engine = new Engine(power);
}
}
public class NestedTrap {
public static void main(String[] args) {
Car car = new Car(100);
tune(car);
System.out.println("After method call: " + car.engine.power);
}
public static void tune(Car c) {
c.engine.power = 200;
c = new Car(50);
}
}
Output:
After method call: 200
Why didn’t the reassignment inside the method reset the engine’s power to 50?
Because:
- The modification
c.engine.power = 200changes the sharedEngineobject’s state. - The reassignment
c = new Car(50)only changes the local copy of the reference.
Once again, Java’s rule is consistent: you can change the internal state of a shared object, but you can’t rebind the caller’s reference.
How to Handle These Questions in Interviews
When you face any of these examples in an interview, here’s a simple reasoning pattern that will help you answer confidently:
- Ask yourself what’s being passed: Is it a primitive or a reference?
- Remember that the reference is copied: The method can use the same object but can’t change which object the caller points to.
- Check what the method is doing: Is it mutating the object (state change) or reassigning the reference (binding change)?
- Predict the output logically: Based on the above, determine which effects persist outside the method.
Once you internalize these steps, no “pass by reference” trick question can throw you off balance again.
The Final Verdict
After exploring how Java handles primitives, objects, arrays, and collections — and after dissecting common misconceptions — the verdict is clear: Java is always pass by value. There’s no special exception, no hidden pass by reference mechanism, and no case where a method directly manipulates a caller’s variable. What changes from case to case is what the value being passed represents — either a raw primitive value or a reference to an object in memory.
This final section ties everything together by explaining what this means practically for developers, how to think about Java’s parameter-passing behavior, and what habits to adopt to avoid mistakes.
Java Is Always Pass by Value
Let’s restate the rule once more, clearly and simply:
When a method is called in Java, the values of the arguments are copied and passed to the method’s parameters.
- For primitive types, the actual numerical or boolean value is copied.
- For objects, the reference (the address in memory) is copied.
That’s it — no exceptions. This consistency makes Java predictable, even if it sometimes leads to counterintuitive results.
Here’s a mental shortcut that works every time:
“Java never passes objects — it passes copies of references to them.”
This single sentence can help you predict how any method call will behave.
Thinking in Terms of Copies
Every time you call a method, imagine you’re giving it a duplicate of what’s in your variable’s box.
- If it’s a primitive box, the method gets a separate box with the same number.
- If it’s an object box, the method gets a separate box that points to the same object in memory.
That’s why modifying the object’s internal state works (the boxes both point to the same heap object), but reassigning the reference doesn’t (you only change your local box).
This analogy helps you visualize what’s happening without diving deep into technical memory diagrams.
Example Recap
Here’s a simple recap example that demonstrates both behaviors together:
class Point {
int x;
Point(int x) { this.x = x; }
}
public class Recap {
public static void main(String[] args) {
int num = 5;
Point p = new Point(10);
modifyValues(num, p);
System.out.println("After call: num = " + num + ", p.x = " + p.x);
}
public static void modifyValues(int n, Point pt) {
n = 20; // local primitive copy changed
pt.x = 99; // shared object field changed
pt = new Point(50); // local reference reassigned
}
}
Output:
After call: num = 5, p.x = 99
Explanation:
- The primitive
numwas copied, so its change inside the method didn’t persist. - The object
phad its field modified through a copied reference, so the change persisted. - The reassignment
pt = new Point(50)didn’t affectp, because the reference itself was copied, not shared.
This example summarizes the entire concept perfectly in just a few lines.
Why Java’s Approach Is Safer
Java’s design choice to use pass by value — even for object references — wasn’t accidental. It was meant to eliminate some of the dangers of languages like C and C++, where pass by reference and raw pointers can lead to unpredictable side effects.
By keeping all variable references local to their scope, Java enforces:
- Encapsulation of data: Methods can’t accidentally overwrite variables in other scopes.
- Predictable behavior: Developers know that passing arguments never changes the binding of their original variables.
- Thread safety at the language level: Copies of references reduce unintended cross-thread side effects.
This balance between simplicity and flexibility is one of the reasons Java became so popular — developers can safely pass objects to methods without worrying about unintended reassignments.
When to Return Values Instead
Because Java doesn’t allow direct modification of caller variables, the common pattern for methods that need to “change” something is to return a new value or modify the object’s internal state.
Here’s how you can handle both scenarios safely.
Case 1: Returning a New Value (for Primitives and Immutable Objects)
public static int increment(int num) {
return num + 1;
}
Usage:
int a = 5;
a = increment(a);
System.out.println(a); // 6
This approach makes your intent explicit: the method produces a new value, and you decide whether to keep it.
Case 2: Modifying Object Fields (for Mutable Objects)
class Counter {
int count;
void increment() {
count++;
}
}
Usage:
Counter c = new Counter();
c.increment();
System.out.println(c.count); // 1
This is safe because the object’s state changes, not the reference. Both caller and method refer to the same instance.
Java gives you both choices — immutability for safety and mutability for flexibility — while still following the same pass-by-value rule underneath.
The “Copy of Reference” Mindset
The easiest way to permanently resolve the confusion is to replace “pass by reference” in your mental vocabulary with “copy of reference.”
Whenever you see code like:
modify(obj);
You can instantly translate it to:
“The method gets a copy of the reference stored in
obj.”
From there, it’s easy to reason:
- If the method changes the object’s internal data → the change persists.
- If the method reassigns its local variable → the change does not persist.
This mental model works 100% of the time.
How to Explain It in an Interview
If you’re asked this in an interview, here’s a concise but clear answer that shows deep understanding:
“Java is always pass by value. For primitives, it passes a copy of the actual value. For objects, it passes a copy of the reference to the object. That’s why you can modify an object’s contents in a method, but you can’t make the caller’s variable point to a new object.”
You can then demonstrate it with a short example like this one-liner:
obj = new Object();
…and explain that the reassignment affects only the local copy of the reference.
This shows that you not only know the rule but understand the reasoning behind it — which is exactly what interviewers look for.
How This Knowledge Helps You as a Developer
Beyond interviews, understanding Java’s pass-by-value mechanism improves your everyday coding in several ways:
- You can confidently design APIs knowing when data changes will persist or not.
- You’ll avoid unexpected side effects when working with shared objects.
- You’ll know when to use immutability versus mutability for better code clarity.
- You’ll understand how garbage collection decisions depend on variable scope and reference lifetimes.
In essence, this concept underpins nearly every operation you perform in Java — from method calls to object management — and mastering it separates confident Java developers from those who rely on guesswork.
Understanding this single principle — that Java always passes arguments by value — brings complete predictability to your code and makes your reasoning about behavior bulletproof.