Module 4 · Methods & Modularity ⏱ 19 min

Pass-by-Value: Primitives vs References

By the end of this lesson you will be able to:
  • Explain why a method cannot reassign a caller's variable, for primitives and references alike
  • Predict when a method can mutate an object's contents through a copied reference
  • Distinguish between pass-by-value and pass-by-reference behaviour

Every method call in Java hands something across. The question is what exactly gets handed over — and the answer is different depending on whether you are passing a primitive or an object. Misunderstanding that difference is the single most common source of confusion in introductory Java: beginners write a method that tries to swap two variables and wonder why nothing changed, or they hand an array to a helper and are surprised when the original comes back sorted. This lesson shows you precisely what Java copies, precisely when the caller's data can change, and why the shorthand phrase 'pass-by-reference' does more harm than good to your mental model.

flowchart LR
  subgraph P["primitive int"]
    A["x = 7"]
  end
  subgraph R["reference StringBuilder"]
    B["s"] --> OBJ["StringBuilder<br/>hello"]
  end
  subgraph PC["pass-by-value copy"]
    AC["copy = 7"]
    BC["copy of s"] --> OBJ2["same object<br/>hello"]
  end
  A -->|"copies value"| AC
  B -->|"copies reference"| BC
  style OBJ fill:#b45309,color:#fff
  style OBJ2 fill:#b45309,color:#fff
Primitives: the box and the value travel together. References: the box holds an arrow; only the arrow is copied.

Primitives: the copy is the whole value

When you pass a primitive — an int, double, boolean, char, or any of their boxed cousins — Java copies the entire value into the parameter slot. The method now owns a completely independent variable that happens to hold the same number. Reassign that parameter and you are overwriting a local temporary; the caller's original variable lives in a different stack frame and is never consulted. This is why a method like addOne(x) cannot make x larger. The parameter is a photocopy, not the original document, and burning the photocopy does not scorch the original. Every primitive call is perfectly safe from side effects.

Main.java — a primitive argument is copied; reassigning the parameter does not reach the caller. Real, compiling Java.
public class Main {
    static void addOne(int n) {
        n = n + 1;
        System.out.println("inside: " + n);
    }

    public static void main(String[] args) {
        int value = 5;
        addOne(value);
        System.out.println("outside: " + value);
    }
}

References: the copy is the arrow, not the house

An object variable does not contain the object itself. It contains a reference — a memory address that points to where the object lives on the heap. When you pass that variable to a method, Java copies the reference by value. The method receives its own private copy of the arrow, but both arrows aim at the exact same house. That distinction is the heart of the matter: the parameter and the caller's variable are two separate references, yet they share one object. If the method mutates the object — appending to a StringBuilder, setting a field, sorting an array — that change is visible to the caller because both references aim at the single shared object in memory.

Main.java — a reference is copied, so both variables see the same object. Real, compiling Java.
public class Main {
    static void appendHello(StringBuilder sb) {
        sb.append("hello");
        System.out.println("inside: " + sb);
    }

    public static void main(String[] args) {
        StringBuilder msg = new StringBuilder("hi ");
        appendHello(msg);
        System.out.println("outside: " + msg);
    }
}
flowchart LR
  A["msg"] --> OBJ["StringBuilder<br/>hi hello"]
  B["sb = msg"] --> OBJ
  A -.->|"same object"| B
  style OBJ fill:#b45309,color:#fff
Two reference variables share one object. Mutating through either reference changes the single object they both point at.

Mutation versus reassignment

Here is the subtlety that separates confident readers from stuck beginners. A method that mutates the shared object reaches the caller, because there is only one object and both references can see it. But a method that reassigns its parameter to a brand-new object merely repoints its own local arrow. The caller's arrow still aims at the original object, completely untouched. sb = new StringBuilder("new") inside a method changes only the local copy of the reference. The caller's msg variable never moves. This is why you cannot write a swap method in Java that exchanges two caller variables by reassigning parameters. The language simply does not provide any mechanism for a method to repoint the caller's variables.

Main.java — reassigning a reference parameter does not change the caller's variable. Real, compiling Java.
public class Main {
    static void reassign(StringBuilder sb) {
        sb = new StringBuilder("new");
        System.out.println("inside: " + sb);
    }

    public static void main(String[] args) {
        StringBuilder msg = new StringBuilder("old");
        reassign(msg);
        System.out.println("outside: " + msg);
    }
}
flowchart LR
  A["msg"] --> OBJ1["old"]
  B["sb"] --> OBJ1
  B -.->|"reassign"| OBJ2["new"]
  style OBJ1 fill:#3776ab,color:#fff
  style OBJ2 fill:#b45309,color:#fff
Reassignment repoints the local reference only. The caller's reference still aims at the original object.

Why 'pass-by-reference' is the wrong label

Some teachers call objects 'pass-by-reference' because mutations are visible. That shortcut causes real damage. True pass-by-reference means the method receives the caller's actual variable and can repoint it. Java never does that — it always copies the value. For object variables, that value happens to be a reference, so the copy aims at the same object. The accurate phrase is pass-by-value of the reference. Keeping the terminology straight protects you from writing code that assumes the caller's variable can be repointed, and from debugging sessions that last hours because you expected reference semantics that Java does not have. Precision in language leads to precision in code.

Exercise

Primitive pass-by-value. A method doubles its parameter. Predict both lines.

public class Main {
    static void doubleIt(int x) {
        x = x * 2;
        System.out.println("method: " + x);
    }

    public static void main(String[] args) {
        int num = 4;
        doubleIt(num);
        System.out.println("main: " + num);
    }
}
Exercise

Reference mutation. A method changes an element of an array. Predict both lines — does the caller see the change?

public class Main {
    static void zeroFirst(int[] nums) {
        nums[0] = 0;
        System.out.println("method: " + nums[0]);
    }

    public static void main(String[] args) {
        int[] values = {7, 8, 9};
        zeroFirst(values);
        System.out.println("main: " + values[0]);
    }
}
Exercise

Reference reassignment. A method points its parameter at a new array. Predict both lines — does the caller change?

public class Main {
    static void replace(int[] nums) {
        nums = new int[]{1, 2, 3};
        System.out.println("method: " + nums[0]);
    }

    public static void main(String[] args) {
        int[] values = {7, 8, 9};
        replace(values);
        System.out.println("main: " + values[0]);
    }
}
Exercise

A method receives an int[] array, sorts it with Arrays.sort(arr), and returns. What happens to the caller's array?

Exercise

Which statement accurately describes Java's parameter passing?

Recap

  • Java is pass-by-value for everything. Primitives receive a true copy of the value; references receive a copy of the reference.
  • Two reference copies aim at the same object, so mutation through one is visible through the other.
  • Reassigning a reference parameter repoints only the local copy; the caller's variable still aims at the original object.
  • True pass-by-reference would let a method repoint the caller's variable — Java does not do this.
  • The accurate description is pass-by-value of the reference, not 'pass-by-reference'.

Next you will learn how to protect your objects from accidental mutation by other methods — immutability and defensive copying.

Checkpoint quiz

A method receives a StringBuilder and calls .append() on it. What happens to the caller's variable?

A method reassigns its StringBuilder parameter to new StringBuilder(). What happens to the caller's variable?

Go deeper — technical resources