Recommended resources
The freely available resources this lesson will direct you to.
Good to see you again. Last lesson established the distinction that makes today’s topic possible: a primitive variable holds a primitive value, while a reference variable holds a reference value that may designate an object.
That same assignment model governs method calls. Java does not make an exception for objects: every argument is passed by value. The apparent paradox is that a copied reference can still designate the same mutable object as the caller’s reference. This lesson gives you a reliable way to trace mutation and reassignment—an interview favorite and a practical debugging skill in Java backend code.
1. The rule: Java always passes arguments by value
When Java calls a method, it evaluates each argument and copies its value into the corresponding parameter variable.
That is true for every kind of argument:
- For
int,boolean, and other primitives, the copied value is the primitive value. - For an object, array, or
String, the copied value is a reference. - For a
nullargument, the copied value isnull.
The parameter is a new local variable belonging to the called method. It is not an alias for the caller’s variable.
Consider primitive values first:
static void increase(int number) {
number = number + 1;
}
public static void main(String[] args) {
int attempts = 3;
increase(attempts);
System.out.println(attempts); // 3
}
At the call site, Java copies the value 3 into the parameter number. Incrementing number changes only that local copy. Once increase returns, its parameter no longer exists.
The official Dev.java explanation is worth reading because it contrasts primitive values with copied object references using the same model.
Calling Methods and Constructors - Dev.java
Read “Calling Methods and Constructors” on Dev.java. It provides the language-level explanation of why primitive parameters do not affect callers, while a method can still change fields of a shared object.
In the section “Passing Primitive Data Type Arguments,” read the primitive argument explanation. Then read “Passing Reference Data Type Arguments,” beginning with the central rule for objects. Focus on the difference between changing a parameter variable and changing the state of the object it currently refers to.
A concise interview answer is:
Java is always pass-by-value. For object arguments, the value being copied is a reference. Both the caller’s variable and the method parameter can initially refer to the same object.
Avoid saying simply “objects are passed by reference.” It describes the observed mutation poorly and is technically wrong in Java.
2. Two separate variables can designate one object
Suppose a backend service passes a mutable request object to a helper method:
class OrderRequest {
String status;
}
static void approve(OrderRequest request) {
request.status = "APPROVED";
}
public static void main(String[] args) {
OrderRequest order = new OrderRequest();
order.status = "PENDING";
approve(order);
System.out.println(order.status); // APPROVED
}
The call does not copy the OrderRequest object. It copies the reference held in order into the parameter request.
Immediately after the method begins:
orderis a variable inmain.requestis a distinct parameter variable inapprove.- Both reference the same
OrderRequestinstance.
The assignment request.status = "APPROVED" changes a field inside that shared object. Therefore the caller observes the change after the method returns.

The stack-and-heap picture is useful for this particular question, but keep it conceptual. The essential language rule is not the physical address of an object; it is that Java copied a reference value, leaving two variables able to reach one object.
For a short visual explanation, watch the central part of Coding with John’s video.
Java is ALWAYS Pass By Value. Here's Why
Watch “Java is ALWAYS Pass By Value. Here's Why” from Coding with John. It uses one mutable object to make the difference between mutation and reassignment visible.
Begin with the claim and setup, which states the rule and introduces the example. Watch the copied reference for the reason an object-field mutation remains visible to the caller. Then watch reference reassignment, pausing at the line that assigns a newly created object to the parameter. Focus on which variable is changed in each case.
3. Mutation changes shared state; reassignment changes only a local parameter
Now add a reassignment to the previous example:
class OrderRequest {
String status;
}
static void process(OrderRequest request) {
request.status = "APPROVED"; // Mutates the original object
request = new OrderRequest(); // Reassigns only the local parameter
request.status = "REPLACED"; // Mutates the new object
}
public static void main(String[] args) {
OrderRequest order = new OrderRequest();
order.status = "PENDING";
process(order);
System.out.println(order.status); // APPROVED
}
Trace it carefully:
orderrefers to the original object whose status is"PENDING".- Java copies the reference into the parameter
request. request.status = "APPROVED"changes the original shared object.request = new OrderRequest()makes only the local parameter designate a different, new object.- Updating the new object to
"REPLACED"has no connection toorder. - The method returns, and the local
requestparameter disappears. orderstill refers to the original object, now with status"APPROVED".
The key distinction is:
| Operation inside a method | Does the caller observe it? | Why? |
|---|---|---|
request.status = "APPROVED" |
Yes | The copied reference and caller reference designate the same mutable object. |
request = new OrderRequest() |
No | Only the method’s local parameter is reassigned. |
request = null |
No | Only the method’s local parameter becomes null. |
request.status = null |
Yes | A field in the shared object is changed. |
A useful mental question when reading an unfamiliar method is:
Is this line assigning to the parameter variable, or is it changing something reached through the parameter?
Compare:
request = null; // Changes local parameter only
request.status = null; // Changes a field in the shared object
That one visual difference—the dot—changes the outcome.
What true pass-by-reference would permit
In a pass-by-reference language model, a method could directly replace the caller’s variable binding. If Java worked that way, this could make order point to a new object:
static void replace(OrderRequest request) {
request = new OrderRequest();
}
But Java does not work that way. request is an independent parameter variable holding a copied reference value. Reassigning it cannot reach backward and replace the caller’s order variable.
4. The model applies to arrays, collections, and nested objects
Arrays and collections are objects, so they follow the same rule.
Arrays
static void clearFirst(int[] values) {
values[0] = 0;
}
static void replaceArray(int[] values) {
values = new int[] {99, 100};
}
public static void main(String[] args) {
int[] scores = {95, 88};
clearFirst(scores);
System.out.println(scores[0]); // 0
replaceArray(scores);
System.out.println(scores[0]); // Still 0
}
values[0] = 0 mutates the original array object. In contrast, values = new int[] {99, 100} changes only the local parameter’s reference.
Collections
import java.util.List;
static void addAuditTag(List<String> tags) {
tags.add("audited");
}
static void replaceTags(List<String> tags) {
tags = List.of("replacement");
}
public static void main(String[] args) {
List<String> tags = new java.util.ArrayList<>();
tags.add("new");
addAuditTag(tags);
System.out.println(tags); // [new, audited]
replaceTags(tags);
System.out.println(tags); // [new, audited]
}
The first method mutates the ArrayList object through its copied reference. The second method merely makes its parameter refer to a different list.
This is especially relevant in service-layer code. A method receiving a mutable DTO, List, Map, or entity can alter state that the caller also sees. Treat that as part of the method’s effective contract, not as an incidental implementation detail.
5. Immutability changes what can be mutated, not how arguments are passed
A String is a reference type, and a String reference is still passed by value. But String objects are immutable: their character content cannot be changed after construction.
static void addPrefix(String name) {
name = "admin-" + name;
}
public static void main(String[] args) {
String username = "sam";
addPrefix(username);
System.out.println(username); // sam
}
The concatenation creates a different String object, and the method reassigns only its local parameter name. It does not mutate the original string, because there is no operation that can mutate a String’s contents.
Contrast that with StringBuilder, which is mutable:
static void addPrefix(StringBuilder name) {
name.insert(0, "admin-");
}
public static void main(String[] args) {
StringBuilder username = new StringBuilder("sam");
addPrefix(username);
System.out.println(username); // admin-sam
}
The parameter is still passed by value in both examples. The different result comes from object mutability, not from a different parameter-passing mechanism.
A precise summary:
- A mutable object can be modified through either of two copied references that designate it.
- An immutable object cannot be modified through any reference.
- A reference variable, whether it refers to a mutable or immutable object, can itself be reassigned—provided the variable is not declared
final. - Parameter reassignment is always local to the method.
6. Returning a replacement is the Java way to update the caller’s reference
If a method intends to provide the caller with a different object, it should return that object and the caller should explicitly assign the result.
static OrderRequest normalize(OrderRequest request) {
if (request == null) {
return new OrderRequest();
}
return request;
}
public static void main(String[] args) {
OrderRequest order = null;
order = normalize(order);
}
This makes the replacement visible because the assignment happens in the caller’s scope.
The same pattern is common with immutable types:
static String normalizeEmail(String email) {
return email.trim().toLowerCase();
}
public static void main(String[] args) {
String email = " Dev@Example.com ";
email = normalizeEmail(email);
System.out.println(email); // dev@example.com
}
The method does not and cannot replace the caller’s email variable silently. Its return value gives the caller the choice to use the replacement.
7. A classic interview trap: swapping references
A common question asks whether this swaps the caller’s variables:
class Box {
String value;
Box(String value) {
this.value = value;
}
}
static void swap(Box first, Box second) {
Box temporary = first;
first = second;
second = temporary;
}
Box left = new Box("left");
Box right = new Box("right");
swap(left, right);
System.out.println(left.value); // left
System.out.println(right.value); // right
It does not swap left and right. Java copied the two reference values into first and second; swapping those local copies has no effect on the caller’s variables.
A method could mutate the two objects’ fields instead:
static void swapValues(Box first, Box second) {
String temporary = first.value;
first.value = second.value;
second.value = temporary;
}
After this method, left still designates the original left Box object and right still designates the original right Box object, but their internal value fields have been exchanged. That is mutation, not reference swapping.
8. Interview Q&A: pass-by-value and object parameters
Q: Is Java pass-by-value or pass-by-reference?
Answer:
Java is always pass-by-value. A method receives its own parameter variable containing a copy of the argument value. When the argument is an object reference, Java copies the reference value, not the object and not the caller’s variable itself.
Q: Why can a method change an object passed to it if Java is pass-by-value?
Answer:
The method receives a copied reference that initially designates the same mutable object as the caller’s reference. Changing a field or invoking a mutating method changes the shared object, so the caller observes the changed state.
Q: Can a Java method reassign an object argument so that the caller’s variable points to a new object?
Answer:
No. Reassigning the parameter changes only the method’s local parameter variable. To give the caller a replacement object, return it and let the caller assign the returned value.
Q: What is the difference between obj = null and obj.field = null inside a method?
Answer:obj = null reassigns the local parameter and does not affect the caller’s reference. obj.field = null mutates a field of the shared object, so the caller can observe that field becoming null.
Q: Does passing an immutable object such as String use a different mechanism?
Answer:
No. A String reference is passed by value like every other reference. The caller does not see a change because String objects cannot be mutated; operations such as concatenation create another string and usually reassign only the local parameter.
Q: Does declaring a parameter final make Java pass by reference?
Answer:
No. final prevents reassignment of that parameter variable within the method. It does not prevent mutation of a mutable object reached through the parameter, and it does not alter Java’s pass-by-value semantics.
The model to retain is compact:
- Java copies every argument value into a new parameter variable.
- For object arguments, the copied value is a reference, so caller and method can initially reach the same object.
- Mutation through the parameter can change shared object state.
- Reassignment of the parameter changes only the method’s local variable.
- If a caller should use a replacement object, the method should return it.
Next, you will move from references and method calls to numeric behavior: widening and narrowing conversions, numeric promotion, overflow, and floating-point results.