Topic 50 of 119 5. Methods in Java Interactive Tutorial

Passing Data to Java Methods: Call by Value Explained

15 mins read Last updated: 22 Aug 2026 28,933 views

Direct Answer & Core Concept

Java passes every argument by value, without exception. There is no call-by-reference in Java, and this is not a matter of interpretation. What makes objects seem to behave differently is that a variable holding an object holds a reference, and what gets copied is that reference value. So when you pass an object, two copies of the reference exist afterwards: yours and the parameter. Both point at the same object on the heap. That is why mutating the object through the parameter changes what the caller sees, while reassigning the parameter to a different object leaves the caller untouched. The method changed its own copy of the address, not the object, and not your copy of the address. Once that picture is clear, every case follows. Reassigning a primitive does nothing to the caller. Reassigning an object parameter does nothing to the caller. Mutating an object the parameter points to is visible everywhere. Returning a new object and assigning it at the call site also works. The interesting cases live at the edges, which is where arrays and the two forms of Integer misbehave, and those are covered below.

Detailed Explanation & Mechanics

1) The Two Things You Can Do With a Parameter

Inside a method you have exactly two moves available, and separating them is the whole topic. You can change what the parameter points to, or you can change the thing it points to.

MoveExampleCaller sees a change?
Reassign the parameternum = 99No
Reassign the parameter to a new objectp = new Person()No
Mutate the object it points top.name = "Bob"Yes
Return a value and assign itnum = double(num)Yes

Only the third row mutates shared state. The other three all work on something local to the method.

2) Primitives Are Simple

A primitive argument is copied outright. The method holds its own variable with its own value, so any change to it is invisible outside.

Main.java
Run Online
public static void bumpPrimitive(int count) {
    count = count + 1;
    // changes the local copy only
}public static void demoPrimitive() {
    int total = 5;
    bumpPrimitive(total);
    System.out.println(total);
    // still 5
}

If you want the caller to see a new value, return it and assign it. That is the honest mechanism for primitives.


Diagram showing a primitive value copied into a method parameter with no link back to the caller
A primitive value is copied. Two independent variables hold the same number, and changing one leaves the other alone.

3) Objects: The Reference Copy

When you declare a variable of a class type, the variable holds a reference. Passing it copies that reference value, so both variables address the same object.

Main.java
Run Online
public class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }
}


Diagram showing a caller reference and a parameter reference both pointing at one shared object on the heap
Two references, one object. Mutating the object is shared; swapping which object a reference names is not.

Main.java
Run Online
public static void mutateObject(Person person) {
    person.name = "Bob";
    // changes the shared object, caller sees it
}public static void replaceObject(Person person) {
    person = new Person("Eve");
    // changes this reference only, caller sees nothing
}

The rule people find hardest: person.name = "Bob" reaches through the reference and modifies the object. person = new Person(...) does not reach through it at all, it just points the local reference somewhere else. Both are assignments in the source, with completely different effects.

4) Both Moves in One Method

Putting both in a single method makes the contrast concrete, because the caller observes one change and not the other.

Main.java
Run Online
public static void modifyValues(int count, Person person) {
    count = 99;
    // local copy changes, caller unaffected
    person.name = "Bob";
    // shared object changes, caller sees it
    person = new Person("Eve");
    // local reference changes, caller unaffected
}public static void demoBoth() {
    int original = 10;
    Person person = new Person("Alice");
    modifyValues(original, person);
    System.out.println(original);
    // 10,  unchanged
    System.out.println(person.name);
    // Bob,  changed by mutation
}

The final assignment is the instructive one. After it runs, the person inside the method points at a brand new object that the caller has never seen and cannot reach.

5) The Three Ways to Change the Caller

If you need the caller to end up with something different, three mechanisms work, and they are worth setting side by side.

MechanismWorks forNote
Mutate a reachable objectObjectsRelies on shared state, so it surprises callers
Return a valueEverythingExplicit and the usual choice
Return a filled containerCollectionsCreates a new object, so no shared state

Prefer returning. It reads clearly at the call site and keeps the caller in control of when state changes.

6) Arrays Behave Like Objects

An array variable is a reference, so arrays follow the object rules exactly. This surprises people who expect arrays to act like long primitives.

Main.java
Run Online
public static void mutateArray(int[] values) {
    values[0] = 99;
    // changes the shared array, caller sees it
}public static void replaceArray(int[] values) {
    values = new int[] {
        1, 2, 3
    };
    // local reference only, caller sees nothing
}public static void demoArray() {
    int[] data = {
        1, 2, 3
    };
    mutateArray(data);
    System.out.println(data[0]);
    // 99, changed
}

This is the single most common practical surprise in the topic, and it is covered at length in passing arrays to methods.

Returning a mutated array and assigning it back is usually clearer than mutating in place, because it makes the change visible at the call site: data = transform(data).

7) Collections and the Trap That Is Not a Trap

A mutable collection passed as an argument behaves like any other object: list.add(item) inside the method is visible to the caller. This is correct, and worth stating plainly, because people often assume it must not work.

Main.java
Run Online
public static void addToList(java.util.List<String> items) {
    items.add("added inside");
    // mutates the shared list, caller sees it
}

What does not work is items = new ArrayList<>() inside the method, because that only repoints the local reference. If you need a different list, return one.

Note that String is the exception that proves the rule. Since it is immutable, the String class has no mutating methods at all, so a passed String behaves exactly like a primitive.

8) Integer Is Not What You Expect

This trips up nearly everyone, because the Integer overload is chosen at compile time while the mutation happens at runtime.

Main.java
Run Online
public static void mutateInteger(Integer count) {
    count = count + 1;
    // unboxes, adds, boxes a NEW object, stores locally
}public static void demoInteger() {
    Integer count = 5;
    mutateInteger(count);
    System.out.println(count);
    // still 5
}

Inside the method, count + 1 unboxes to a primitive, adds, then boxes the result into a brand new Integer. The parameter is then repointed at that new object. The object the caller referenced was never touched, so the caller still sees five.

This is the reason Integer behaves like a primitive despite being an object. The same applies to every wrapper type listed in wrapper classes, and to any immutable object such as a String or a record.

TypeMutating inside a methodWhy
intNot visibleThe value itself is copied
IntegerNot visibleImmutable, so assignment creates a new object
StringNot visibleImmutable, and has no mutating methods
int[]VisibleA reference to a shared array
ArrayListVisibleA mutable object on the heap
A recordNot visibleFields are final, so the object cannot change

9) Common Misconceptions

ClaimVerdict
Java passes primitives by value and objects by referenceFalse. Everything is by value, including references.
Passing an object cannot change the callerFalse. Mutating the object is visible.
Passing a reference lets the method repoint my variableFalse. Only its own copy moves.
Arrays are passed by referenceFalse. The array reference is copied.
Integer is mutable like other objectsFalse. It is immutable.
Passing null lets the method assign my variableFalse. A copy of null is still null.
Final parameters change the passing rulesFalse.final only blocks reassignment.
Method parameters are fields of the objectFalse. They are locals on the stack frame.


Infographic comparing eight common misconceptions about Java pass by value with the correct explanation
Eight myths about Java argument passing. Every one of them fails the same test: what exactly was copied?

Frequently Asked Questions

Does Java use pass by value or pass by reference?


Java uses pass by value exclusively. There is no pass-by-reference mechanism in the language at all. When you pass an object, the reference value held by your variable is copied into the parameter, so both variables address the same object while remaining independent names.

Then why does changing an object inside a method affect the caller?


Because the copied reference still points at the same heap object. Assigning to the parameter, such as person.name, reaches through the reference and modifies that shared object. The caller is looking at the same object, so it observes the change. This is mutation, not reference passing.

Why does assigning to an object parameter not change my variable?


Because the parameter is a separate copy of the reference. The assignment repoints the method copy at a different object, leaving your reference and the original object untouched. To change what the caller holds, return the new value and assign it at the call site.

Are arrays passed by reference in Java?


No. An array variable holds a reference, so arrays follow the same rule as any other object. Mutating an element through the parameter is visible to the caller, but assigning a new array to the parameter is not, since that only moves the method copy of the reference.

Why does changing an Integer parameter not affect the caller?


Because Integer is immutable. The expression count plus one unboxes to a primitive, adds, then boxes the result into a brand new Integer object, which the parameter is then repointed at. The original object is untouched, so the caller still sees the old value, exactly as with a primitive.

Can a method change the variable it was passed?


Not directly, since that would require pass by reference. A method can mutate an object reachable through the parameter, return a value for the caller to assign, or return a container the caller reads. Returning is the clearest of these and makes the change visible at the call site.

Do final parameters change how arguments are passed?


No. A final parameter is passed exactly like any other, and final affects only whether the method may reassign that local name. It does not make an object immutable, and it does not prevent mutation of the object the parameter points to.

How do I pass data so the caller can never be surprised?


Prefer immutable values. Passing a String, a record, or any object whose fields cannot change means a method has no way to alter what the caller holds, regardless of how the argument was passed. This is the reasoning behind records, and it removes a whole class of bugs rather than documenting around them.

Runnable Code Example: Passing Data to Java Methods

Java 17+
Main.java
Run Online
// Demonstrates that every Java argument is passed by value.
public class CallByValueDemo {
    // A mutable object, so mutation is visible to the caller
    public static class Person {
        String name;
        public Person(String name) {
            this.name = name;
        }
    } // An immutable value, which behaves like a primitive
    public record Money(long amountInCents) {
    } private static final int TEN = 10;
    private static final long BASE_AMOUNT = 100L;
    private static final int NINETY_NINE = 99;
    private static final int ONE = 1;
    private static final int TWO = 2;
    private static final int THREE = 3;
    private static final String ALICE = "Alice";
    private static final String BOB = "Bob";
    private static final String EVE = "Eve";
    private static final String ADDED_INSIDE = "added inside";
    private static final String REPLACED_INSIDE = "replaced inside";
    // MUTATION is shared. REASSIGNMENT is not.
    public static void modifyValues(int count, Person person) {
        count = NINETY_NINE;
        // local copy changes, caller unaffected
        person.name = BOB;
        // shared object changes, caller sees it
        person = new Person(EVE);
        // local reference changes, caller unaffected
    } // Arrays follow the object rules exactly
    public static void modifyArray(int[] values) {
        values[0] = NINETY_NINE;
        // shared array, caller sees it
    } public static void replaceArray(int[] values) {
        values = new int[] {
            ONE, TWO, THREE
        };
        // local reference only, caller unaffected
    } // A mutable collection behaves the same way
    public static void addToList(java.util.List<String> items) {
        items.add(ADDED_INSIDE);
        // shared list, caller sees it
    } public static void replaceList(java.util.List<String> items) {
        items = new java.util.ArrayList<>();
        // local reference only
        items.add(REPLACED_INSIDE);
        // into a list the caller cannot reach
    } // An immutable value behaves like a primitive
    public static void mutateInteger(Integer count) {
        count = count + ONE;
        // unboxes, adds, boxes a NEW object
    } public static void mutateMoney(Money amount) {
        amount = new Money(amount.amountInCents() + ONE);
        // records cannot change
    } // RETURNING is the explicit mechanism the caller controls
    public static int doubled(int value) {
        return value * TWO;
    } public static void main(String[] args) {
        int original = TEN;
        Person person = new Person(ALICE);
        modifyValues(original, person);
        System.out.println("primitive after: " + original);
        // 10,  unchanged
        System.out.println("person after:    " + person.name);
        // Bob,  changed
        int[] data = {
            ONE, TWO, THREE
        };
        modifyArray(data);
        System.out.println("array after:     " + data[0]);
        // 99,  changed
        java.util.List<String> items = new java.util.ArrayList<>();
        items.add(ALICE);
        addToList(items);
        replaceList(items);
        System.out.println("list after:      " + items.size());
        // 2, only the add landed
        Integer boxed = TEN;
        mutateInteger(boxed);
        System.out.println("Integer after:   " + boxed);
        // 10,  unchanged
        Money amount = new Money(BASE_AMOUNT);
        mutateMoney(amount);
        System.out.println("record after:    " + amount.amountInCents());
        // unchanged
        // RETURNING is visible at the call site
        original = doubled(original);
        System.out.println("returned value:  " + original);
        // 20
        // THESE ARE ALL COMPILE ERRORS AND ARE SHOWN AS COMMENTS
        //
        // assigning to a final parameter
        // public static void finalParam(final int value) {
        //     value = NINETY_NINE;   // cannot reassign a final parameter
        // }
        //
        // trying to mutate an immutable value
        // public static void mutateString(String text) {
        //     text.concat(" more");   // returns a new String, changes nothing
        // }
        //
        // expecting the reassignment of a parameter to reach the caller
        // public static void noEffect(String text) {
        //     text = REPLACED_INSIDE;   // the caller keeps its own value
        // }
    }
}/*
    ACTUAL OUTPUT:

        primitive after: 10
        person after:    Bob
        array after:     99
        list after:      2
        Integer after:   10
        record after:    100
        returned value:  20

    THE NINE THINGS TO REMEMBER
    1) JAVA IS PASS BY VALUE, ALWAYS. There is no pass by reference in
       the language, and objects are not an exception.
    2) WHEN YOU PASS AN OBJECT, THE REFERENCE IS COPIED. Both names
       point at the same object, but they are separate variables.
    3) MUTATION IS SHARED. Assigning THROUGH the reference, such as
       person.name, changes the object both names see.
    4) REASSIGNMENT IS NOT SHARED. Assigning TO the parameter, such as
       person = new Person(), moves only the method copy.
    5) ARRAYS ARE OBJECTS, so they follow the same rules. Mutating an
       element is visible; assigning a new array to the parameter is not.
    6) Integer IS IMMUTABLE, so count plus one unboxes, adds, then
       boxes a brand new object. The caller sees the original value,
       exactly as with a primitive. String and records behave the same.
    7) A FINAL PARAMETER ONLY BLOCKS REASSIGNMENT of that local name.
       It does not change how the argument is passed, and it does not
       stop the method mutating the object behind it.
    8) TO LET THE CALLER KEEP A DIFFERENT VALUE, RETURN IT. Assigning at
       the call site makes the change obvious to every reader.
    9) FOR DATA THAT MUST NOT SURPRISE ANYONE, PASS IMMUTABLE VALUES.
       A String or a record gives a method no way to change what the
       caller holds, which removes a whole class of bugs.
*/

Common Java Technical Interview Questions & Answers

Interview Prep
Q1: Does Java use pass by value or pass by reference?\nA1: Java uses pass by value exclusively. There is no pass-by-reference mechanism in the language at all. When an object is passed, the reference value held by the variable is copied into the parameter, so both names address the same object while remaining independent variables.\nQ2: If Java is pass by value, why does changing an object inside a method affect the caller?\nA2: Because the copied reference still points at the same object on the heap. Assigning through the reference, such as person.name, modifies that shared object, and the caller is looking at the same object so it observes the change. That is mutation of shared state rather than reference passing.\nQ3: Why does assigning to an object parameter not change the caller variable?\nA3: Because the parameter holds its own copy of the reference, so the assignment repoints only the method copy at a different object. The caller reference and the original object are untouched. To change what the caller holds, return the value and assign it at the call site.\nQ4: Are arrays passed by reference in Java?\nA4: No. An array variable holds a reference, so arrays follow the same rule as any other object. Mutating an element through the parameter is visible to the caller, while assigning a new array to the parameter is not, since that moves only the method copy of the reference.\nQ5: Why does modifying an Integer parameter not affect the caller?\nA5: Because Integer is immutable. The expression unboxes to a primitive, adds, then boxes the result into a brand new Integer object that the parameter is repointed at. The original object is never modified, so the caller sees the old value, exactly as with a primitive. String and records behave the same way.\nQ6: What is the difference between mutating an object and reassigning a parameter?\nA6: Mutating an object reaches through the reference with an assignment to a field or an element, which changes the shared object and is visible to the caller. Reassigning the parameter changes only which object that local name refers to, so the caller sees nothing. Both look like assignments in the source but have opposite visibility.\nQ7: Can a final parameter change how an argument is passed?\nA7: No. A final parameter is passed exactly like any other parameter, and final affects only whether the method may reassign that local name. It does not make the referenced object immutable and does not prevent mutation of the object the parameter points to.\nQ8: How can I design a method so the caller is never surprised by a change?\nA8: Prefer immutable arguments. Passing a String, a record, or any object with final fields gives the method no way to alter what the caller holds, since there is no mutating operation to call. Alternatively return the new value and assign it at the call site, which makes the change visible in the code rather than hidden inside the method.

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Return values and assign them at the call site, rather than relying on a method to mutate an object the caller still holds.\nPrefer immutable argument types such as String and records when a method has no need to change what it is given.\nUse mutable objects deliberately when the caller should see changes, and document that expectation on the method.\nAvoid depending on mutation as an implicit output channel, since it makes the effect invisible at the call site.\nNever expect a method to repoint your variable, since assignment to a parameter only moves the method copy.\nRemember that a final parameter blocks reassignment but not mutation, so do not treat it as making the object immutable.\nTreat an Integer, String, or record argument as safe by construction, because immutability removes any way to alter it.\nReturn a new collection instead of mutating the one passed in, unless in-place mutation is clearly the intent.\nWhen mutating an argument is the intent, say so in the method name, such as sortInPlace, so the effect is obvious.\nCopy a collection before mutating it when the caller may still need the original contents.\nRemember that arrays are objects, so a method that replaces the array parameter leaves the caller pointing at the old one.\nPrefer records for data carriers, since their final fields remove a class of pass-by-value surprises by construction.
We value your privacy & transparency

OperateTools uses cookies and local storage to ensure basic tool functionality, preserve preferences, and analyze aggregated traffic. We never sell your personal data or index your document files. Learn more in our Privacy Policy.