Java Is Pass-by-Value, Not Pass-by-Reference: Examples

Java pass by value explained with primitives, object references, swap(), Strings, arrays and lists, plus how to return changed values from a method.

Caller variable and method parameter pointing to the same Player object, and the parameter pointing to a new object after reassignment

Java is always pass-by-value, so a method gets a copy of each argument, and for an object argument that copy is the reference to the object, not the object itself. The method can change the object through the copied reference, but assigning a new object to the parameter never changes the caller’s variable.

The pass-by-value rule explains the two results that confuse most developers. A method that calls a setter on its argument changes the caller’s object, while a method that assigns a new value to its parameter has no effect outside.

The following example passes an int and a Player object to three methods that we write in section 2.

int points = 10;
addBonus(points);
int afterBonus = points;              // 10, the method changed its own copy
Player anna = new Player("Anna");
rename(anna);
String renamed = anna.name();         // "Annie", the method changed the shared object
replace(anna);
String stillAnnie = anna.name();      // "Annie", the new object stayed inside the method

Notice that only rename() has a visible effect, because it changes the object instead of the parameter. We define both terms, walk through primitives and references with a diagram, show why swap() cannot work, and cover strings, arrays and ways to return changed values.

1. Pass-by-Value vs Pass-by-Reference

In pass-by-value, the method gets a copy of the value of each argument, and the parameter is a new variable. In pass-by-reference, the parameter is another name for the caller’s variable, so assigning to the parameter assigns to the caller’s variable. C++ supports both, with int& x declaring a reference parameter, while Java supports only the first.

When a method is called, the values of the argument expressions initialize newly created parameter variables before the method body runs. For a variable of a class type, that value is a reference, which is a pointer to the object.

Pass-by-value (Java)Pass-by-reference (C++ & parameters)
What the method getsA copy of the argument’s valueAn alias of the caller’s variable
Assigning to the parameterChanges only the copyChanges the caller’s variable
Changing an object through the parameterVisible to the caller, because both point to the same objectVisible to the caller
A working swap(a, b)Not possiblePossible

The phrase “objects are passed by reference” is wrong in Java, because the reference is passed by value. The difference shows as soon as a method assigns to its parameter, as the next sections show.

2. Passing Primitives and Object References

A primitive variable such as int holds the number itself, so the method gets a copy of the number. Any change to the parameter stays inside the method.

static void addBonus(int points) {
    points = points + 5;
}

A variable of a class type holds a reference. The method gets a copy of that reference, so the caller’s variable and the parameter point to the same object until one of them is assigned something else. The following example uses a small mutable Player class and two methods that treat their parameter differently.

class Player {
    private String name;

    Player(String name) {
        this.name = name;
    }

    String name() {
        return name;
    }

    void setName(String name) {
        this.name = name;
    }
}
static void rename(Player p) {
    p.setName("Annie");
}
static void replace(Player p) {
    p = new Player("Bob");
    p.setName("Bobby");
}
Caller variable and method parameter pointing to the same Player object, and the parameter pointing to a new object after reassignment
The parameter p starts as a copy of the reference in anna; reassigning p moves only the copy, so anna still points to the original object

We can follow the two calls step by step.

  • When rename(anna) starts, Java creates the parameter p and copies the reference from anna into it. Both variables point to the same Player object.
  • The call p.setName(“Annie”) changes that shared object, so anna.name() returns “Annie” after the method returns.
  • When replace(anna) starts, p again gets a copy of the reference. The assignment p = new Player(“Bob”) makes p point to a new object, while anna keeps its old reference.
  • The call p.setName(“Bobby”) changes the new object only, and the new object becomes garbage when the method returns.

Java creates the parameter p with the copied value already in it, before the first line of the method body runs, and the same happens in every method invocation.

3. Why a swap() Method Cannot Work in Java

The classic test for pass-by-reference is a method that swaps two variables of the caller. In Java, it swaps only its own copies of the references.

static void swap(Player a, Player b) {
    Player temp = a;
    a = b;
    b = temp;
}
Player left = new Player("Lee");
Player right = new Player("Ria");
swap(left, right);
String leftName = left.name();          // "Lee"
String rightName = right.name();        // "Ria"

In C++, void swap(Player& a, Player& b) would swap the caller’s variables, because the parameters are aliases. In Java, we swap values inside a container that both sides share, such as two positions in an array or a list.

static void swapAt(Player[] team, int i, int j) {
    Player temp = team[i];
    team[i] = team[j];
    team[j] = temp;
}
Player[] team = {new Player("Lee"), new Player("Ria")};
swapAt(team, 0, 1);
String firstPlace = team[0].name();      // "Ria"

The array version works because the method changes the array object, not the caller’s variable team. The article on how to swap two numbers shows the same idea for primitives.

4. Passing Strings and Wrapper Objects

Strings and wrapper objects such as Integer are passed the same way as any other object, but they are immutable. A method cannot change them through the reference, and every operation that looks like a change creates a new object and assigns it to the parameter.

static void shout(String text) {
    text = text + "!";
}
static void shout(StringBuilder text) {
    text.append("!");
}
String greeting = "Hi";
shout(greeting);
String plain = greeting;                   // "Hi"
StringBuilder builder = new StringBuilder("Hi");
shout(builder);
String built = builder.toString();         // "Hi!"

We can see that the String version has no effect outside, because text + “!” creates a new String and assigns it to the parameter. The StringBuilder version changes the shared object. The same applies to Integer, where count++ inside a method unboxes, adds one and assigns a new Integer to the parameter.

5. Arrays and Collections as Arguments

Arrays and collections are objects, so a method can change their elements, and the caller sees the change. A method cannot make the caller’s variable point to a different array or list.

Changed elements cause real bugs. Say a game server has a topThree() method that sorts the list it gets to pick the best scores. The caller passes its list of scores in arrival order, and after the call the list is sorted, so the next feature that reads it in arrival order shows the wrong data.

static List<Integer> topThree(List<Integer> scores) {
    scores.sort(Comparator.reverseOrder());
    return scores.subList(0, Math.min(3, scores.size()));
}
List<Integer> arrivals = new ArrayList<>(List.of(40, 90, 10, 70));
List<Integer> best = topThree(arrivals);
String bestNow = best.toString();          // "[90, 70, 40]"
String arrivalNow = arrivals.toString();   // "[90, 70, 40, 10]", caller's order is gone

The safe version copies the argument before changing it, so the caller’s list stays as it was. A stream does the same in one expression without touching the source.

static List<Integer> topThreeSafe(List<Integer> scores) {
    return scores.stream()
        .sorted(Comparator.reverseOrder())
        .limit(3)
        .toList();
}
List<Integer> arrivals2 = new ArrayList<>(List.of(40, 90, 10, 70));
List<Integer> best2 = topThreeSafe(arrivals2);
String best2Now = best2.toString();          // "[90, 70, 40]"
String arrival2Now = arrivals2.toString();   // "[40, 90, 10, 70]"

Classes that must protect their state copy collections in the constructor and in getters for the same reason, as the guide on the immutable class explains.

6. Returning a Changed Value to the Caller

Because Java cannot assign to the caller’s variable, a method that computes a new value returns it, and the caller assigns the result. This is the normal style for primitives, strings and immutable objects.

static int withBonus(int points) {
    return points + 5;
}
int score = 10;
score = withBonus(score);
int newScore = score;                         // 15

A method that must return several values returns a record instead of changing its arguments. That keeps the data flow visible at the call site.

record Result(int points, String rank) {}
static Result finish(int points) {
    int total = points + 5;
    return new Result(total, total >= 15 ? "gold" : "silver");
}
Result result = finish(10);
int finalPoints = result.points();            // 15
String rank = result.rank();                  // "gold"

Mutable holders, such as a one-element array or an AtomicInteger passed as an argument, also work, but they hide the change inside the method. We keep them for cases such as counters updated from lambdas, where a local variable must be effectively final.

7. Java Pass-by-Value FAQs

Most confusion comes from the word “reference” meaning two different things, a value that points to an object and a parameter-passing mode.

7.1. Is Java pass-by-reference for objects?

No. Java passes a copy of the reference, which is a value. The method can use the copy to change the object, but it cannot make the caller’s variable point to another object, which pass-by-reference would allow.

7.2. Why does my object change after I pass it to a method?

Because the method called a method or set a field on the object, and the caller’s variable points to the same object. To avoid it, pass a copy, or make the class immutable so no method can change it.

7.3. Are arrays passed by reference in Java?

No. An array is an object, so the method gets a copy of the reference to the array. Changing arr[0] inside the method is visible to the caller, while arr = new int[3] is not, as section 5 shows.

7.4. Does a final parameter change anything for the caller?

No. Declaring a parameter final only stops the method from assigning a new value to its own copy. The method can still change the object the parameter points to, and the caller sees no difference.

8. Conclusion

Java passes every argument by value. A primitive parameter gets a copy of the number, and an object parameter gets a copy of the reference, which still points to the caller’s object.

A method can change the object through that copy, so setters, append() and list sorting are visible to the caller. Assigning to the parameter is never visible, which is why swap() fails and why strings and wrappers look unchanged after a call.

When a method computes something new, we return it, or return a record for several values, and we copy collections before changing them. The classes and objects article explains references in more detail, the object-oriented programming guide covers the bigger picture, and the Java tutorial lists the next topics.

9. References

Happy Learning !!

Source Code on Github

Leave a Comment

  1. In step 5, the value is actually changed within the scope of the changeReference() function.
    If you were to print out the value inside the scope of the function, the value will be “b”.
    This is because the variable a only has local scope since it is declared in the signature of the function. At the end of the function, a goes out of scope, unhiding/unshadowing the original variable.

    While this example is correct, I think the confusion with Java isn’t PassByValue vs. PassByReference. Its PassByReference vs. Using Objects By Reference. Java doesn’t pass values as arguments by reference, however it does very much .use objects by reference for performance optimization reasons.

    • This is correct. Here is what happens:

      Original memory address of f: Foo@38cccef
      Original value of f: f

      Inside changeRef scope, before change:
      Memory address = Foo@38cccef
      Value = f

      Inside changeRef scope, after change:
      Memory address = Foo@64b8f8f4
      Value = b

      Inside modifyRef scope, before change:
      Memory address = Foo@38cccef
      Value = f

      Inside modifyRef scope, after change:
      Memory address = Foo@38cccef
      Value = c

  2. Very good explanation

    I have confusion in the point 2 line “initially assigned to null”

    But when i print the reference before assignment initially it point the reference of f.

    public static void changeReference(PassByValue a) {
    		System.out.println("Before Reference of a "+a);
    		PassByValue b = new PassByValue("b");
    		a = b;
    		System.out.println("Reference of a "+a);
    		System.out.println("Reference of b "+b);
    	}
    

    Please correct me if i am wrong.

    • It will not be assigned to NULL. Actually a reference is created and the instance is assigned to it and passed to the method.

      Foo a = f; — this will happen behind and passed the reference to the method

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.