Skip to main content
Java

Why Do Java Methods Modify Objects but Not Primitives?

5 mins

A person receiving a copy of a document (primitive) versus receiving shared access to a binder (object).

What is Pass-By-Value? #

Consider the following Java method, where a String variable is set to null and then passed to the method.

public void exampleMethod(String str1, String str2) {
    str1 = new String("Hello, World!");
    str2 = new String("Goodbye, World!");
}

String myString1 = null;
String myString2 = new String("Initial Value");
exampleMethod(myString1, myString2);
System.out.println(myString1); // null or "Hello, World!"
System.out.println(myString2); // "Initial Value" or "Goodbye, World!"

What would you expect the output to be? If you are new to Java, you might expect the output to be:

Hello, World!
 Goodbye, World!

However, the actual output is:

null
 Initial Value

The String variables myString1 and myString2 remain unchanged even though the method exampleMethod assigned new values to its parameters str1 and str2. This is because Java uses pass-by-value for parameter passing.

In Java, whenever you pass a variable to a method, it is always passed by value. For primitive types (like int, double, boolean, etc.), this behavior is straightforward: a copy of the variable’s value is made and passed to the method. Any changes made to the parameter inside the method do not affect the original variable.

In the example, when the String is assigned a new value inside the method, a new String object is created in memory, and the parameter str1 now points to this new object. However, the original variable myString1 outside the method still points to null, so it remains unchanged. The same logic applies to myString2.

What is Pass-By-Reference? #

What if Java were pass-by-reference?

If primitives were passed by reference, then any changes made to the parameter inside the method would directly modify the original variable. For example:

public void modifyPrimitiveByReference(int num) {
    num = 42;
}
int myNum = 10;
modifyPrimitiveByReference(myNum);
System.out.println(myNum); // 42 if Java were pass-by-reference, but actually prints 10

What about Objects? #

However, what if you are passing an object, like a List? Do modifications to the object inside the method affect the original object?

public void modifyList(List<String> list) {
    list.add("Next Item");
}

List<String> myList = new ArrayList<>();
myList.add("Initial Item");
modifyList(myList);
System.out.println(myList);

The answer is yes.

After the method call, the list does indeed have two items: ["Initial Item", "Next Item"], rather than just the one original item, ["Initial Item"]. This might seem like Java is passing objects by reference, but it is still pass-by-value.

When a variable is assigned to an object, the object is created in memory (the heap), and the variable holds a reference (or pointer) to that object, i.e. the location of the object in memory.

Java passes copies of object locations

When you pass the variable to a method, a copy of the reference (location) is made and is passed to the method. The parameter inside the method now holds a copy of the memory location of the object. So when a modification is made to the object via the parameter, the change occurs on the object in memory.

When control returns to the calling method, the original variable still holds the same reference (location) to the object in memory that was modified by the method.

Reassigning Object Parameters #

If you reassign the parameter to a new object inside the method, the original variable outside the method remains unchanged because it still points to the original object in memory.

public void reassignList(List<String> list) {
    list = new ArrayList<>(); // Reassign to a new object
    list.add("First Item");
}

List<String> myList = new ArrayList<>();
myList.add("Initial Item");
reassignList(myList);
System.out.println(myList); // Still prints ["Initial Item"]

Overcome with Return Values

If you want to reassign an object parameter and have that change reflected outside the method, you can return the new object from the method and assign it to the original variable.

public List<String> reassignList(List<String> list) {
    list = new ArrayList<>(); // Reassign to a new object
    list.add("First Item");
    return list;
}

List<String> myList = new ArrayList<>();
myList.add("Initial Item");
myList = reassignList(myList);
System.out.println(myList); // Now prints ["First Item"]

Immutable Objects #

Immutable objects, like String and wrapper classes (Integer, Double, etc.), cannot change their state after they are created. When you modify an immutable object, a new object is created in memory, and the reference is updated to point to this new object. The original object remains unchanged.

public void modifyString(String str) {
    str = str + " World"; // Creates a new String object
}

String myString = "Hello";
modifyString(myString);
System.out.println(myString); // Still prints "Hello"

Overcome with Mutable Objects

If you do want to have methods modify string-like objects, use a mutable object, like StringBuilder.

The final Keyword #

Many developers assume that using the final keyword on method parameters will stop modifications to the object. However, final only prevents reassignment of the parameter itself; it does not prevent modifications to the object’s state.

public void modifyFinalList(final List<String> list) {
    list.add("New Item"); // Allowed, modifies the object
    list = new ArrayList<>(); // Not allowed, cannot reassign final parameter
}

Summary #

Java is always pass-by-value.

If there is one thing you should always remember for Java, it is that Java is always pass-by-value.

The confusion arises when passing objects, because what is passed is a copy of the reference (location) to the object in memory. This allows methods to modify the state of the object, but reassigning the parameter to a new object does not affect the original variable outside the method.

Keep this distinction in mind when working with methods and parameters in Java to avoid hard-to-track errors in your code.