Skip to content
Java oops misc 6 min read

Call by Value

Java has exactly one way to pass arguments to a method: call by value. Every time you call a method, Java copies the value you pass and gives that copy to the method. The original variable is never touched. This single rule explains a lot of surprising behavior — especially when you start working with objects.

What “Call by Value” Actually Means

When you write doSomething(x), Java evaluates x, copies that value into a new local slot inside doSomething, and the method works with that copy. No matter what the method does to its parameter, your original x is safe.

The confusion usually comes from this: what is the “value” of an object variable? It is not the object itself — it is the reference (memory address) to the object. Java copies that address, so the method gets a copy of the reference, not a copy of the object.

This distinction is everything:

What you passWhat gets copiedCan method change your local variable?Can method mutate the object’s fields?
int, double, boolean, etc.The raw numeric/boolean valueNoN/A
An object reference (String, array, custom class, …)The memory address (reference)NoYes, if the object is mutable

Primitives: The Simple Case

With a primitive, the copy is just a number. Changing it inside the method has zero effect outside.

public class PrimitiveDemo {

    static void doubleIt(int n) {
        n = n * 2;           // modifies only the local copy
        System.out.println("Inside method: " + n);
    }

    public static void main(String[] args) {
        int value = 10;
        doubleIt(value);
        System.out.println("After method:  " + value);
    }
}

Output:

Inside method: 20
After method:  10

value is still 10 because the method only modified its own copy of n.

Objects: The Reference Copy

With an object, the reference is copied — not the object. Both the caller and the method point to the same object in memory, so mutations to the object’s fields are visible to everyone.

class Counter {
    int count = 0;
}

public class ObjectDemo {

    static void increment(Counter c) {
        c.count++;           // mutates the real object — both sides see this
    }

    public static void main(String[] args) {
        Counter myCounter = new Counter();
        System.out.println("Before: " + myCounter.count);
        increment(myCounter);
        System.out.println("After:  " + myCounter.count);
    }
}

Output:

Before: 0
After:  1

c inside increment is a copy of the reference. It still points to the same Counter object, so c.count++ is visible back in main.

Reassigning the Parameter Has No Effect Outside

Here is where people often expect “call by reference” behavior but don’t get it. If you reassign the parameter variable itself (make it point to a different object), the caller’s variable is unaffected.

class Box {
    String label;
    Box(String label) { this.label = label; }
}

public class ReassignDemo {

    static void replaceBox(Box b) {
        b = new Box("New Box");  // only reassigns the local copy of the reference
        System.out.println("Inside method: " + b.label);
    }

    public static void main(String[] args) {
        Box original = new Box("Original");
        replaceBox(original);
        System.out.println("After method:  " + original.label);
    }
}

Output:

Inside method: New Box
After method:  Original

original still points to the same Box it always did. The assignment b = new Box(...) only changed the method’s local copy of the reference.

Strings Look Immutable — and They Are

Strings deserve a special mention. They are objects, so a reference copy is passed. But String is immutable — none of its methods can change the characters inside. Any operation that “changes” a string actually returns a brand-new String object, leaving the original untouched.

public class StringDemo {

    static void appendWorld(String s) {
        s = s + " World";    // creates a new String, assigns to local s
        System.out.println("Inside method: " + s);
    }

    public static void main(String[] args) {
        String greeting = "Hello";
        appendWorld(greeting);
        System.out.println("After method:  " + greeting);
    }
}

Output:

Inside method: Hello World
After method:  Hello

Arrays Are Objects Too

An array variable holds a reference to an array on the heap. Passing it to a method means the method can freely read and modify the array’s elements.

import java.util.Arrays;

public class ArrayDemo {

    static void doubleAll(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            arr[i] *= 2;
        }
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 4};
        doubleAll(nums);
        System.out.println(Arrays.toString(nums));
    }
}

Output:

[2, 4, 6, 8]

The method mutated the real array because arr and nums point to the same heap allocation. For more on arrays, see Arrays.

Under the Hood

When you call a method, the JVM creates a new stack frame for it. Each parameter in that frame gets a copy of the value from the caller’s frame:

  • Primitive parameters — the JVM pushes the raw bits (e.g., 32 bits for an int) onto the new frame’s operand stack. No heap memory is involved; there is simply no way for the callee to reach the caller’s slot.
  • Reference parameters — the JVM pushes the 4- or 8-byte heap address onto the new frame. Both frames now hold the same address, so both can navigate to the same object. The address itself, however, lives in two separate stack slots.

When the method returns, its entire frame is popped and discarded. Any reassignment the method made to its local parameter slot disappears with it; the caller’s slot is untouched.

This model has a significant performance benefit: the JVM never has to deep-copy objects on method calls, which would be expensive. You get cheap calls and predictable semantics — at the cost of needing to think carefully about shared mutable state.

Warning: Because object references are shared, a method that mutates its argument changes state that the caller might not expect to change. Prefer immutable classes or defensive copies when you want to isolate changes.

Tip: If you need a method to “return” multiple values, use a mutable holder object, an array, a record, or simply design the method to return a value rather than modify its parameter.

Common Pitfalls

  • Expecting primitives to be updated by a method — they won’t be. Use a return value or a mutable wrapper object.
  • Expecting reassigning an object parameter to affect the caller — it won’t. Only field mutations on the shared object are visible.
  • Assuming String behaves like a mutable objectString is immutable; concatenation always produces a new object.
  • Passing an array and assuming a fresh copy is made — it isn’t; the method shares the same array.

Simulating “Call by Reference” in Java

Java does not have call by reference, but you can get similar results in two ways:

  1. Return the new value from the method and reassign it in the caller.
  2. Wrap the value in a mutable object (e.g., a single-element array, AtomicInteger, or a custom holder class).
// Pattern 1 – return the new value
static int doubled(int n) {
    return n * 2;
}

// Pattern 2 – mutable wrapper
static void doubleInPlace(int[] holder) {
    holder[0] *= 2;
}

Both patterns are idiomatic Java and are far clearer than any pointer-style trick.

  • Methods — where parameters are declared and how method signatures work
  • Recursion — a pattern that relies heavily on understanding how stack frames and value copies work
  • Wrapper Classes — box primitives into objects; useful when you need mutable holders
  • String Immutability — why String operations never modify the original
  • Immutable Class — how to design objects that are safe to share across method calls
  • Arrays — passed by reference copy, so methods can mutate their contents
Last updated June 13, 2026
Was this helpful?