OOP Basics ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-07-28

Passing Objects to Methods in Java

Objects are passed to methods exactly the same way primitive values are, yet the outcome looks different: changes made to an object's fields inside a method are visible to the caller, while changes to a primitive parameter are not. Let's see why that happens, where this rule stops working the way beginners expect, and how a method returns an object.

Pass by Value or Pass by Reference?

Java always passes arguments by value. For a primitive parameter the value itself is copied (10, 3.14, true). For an object parameter the reference is copied — the address of the object on the heap, not the object itself.

That is exactly why the copied reference still points to the very same object: through it the method can change the object's state, and everyone holding a reference to that object sees the change. In everyday conversation this model is often described as "objects are passed by reference". It is a convenient shortcut, but it is not accurate, and in an interview it is worth spelling out.

The precise wording

Java has no pass-by-reference like C++ or C#. What travels into the method is a copy of the reference. Two consequences follow: you can change the state of an object inside a method, but you cannot make the caller's variable point at a different object.

Example 1. Changing a Primitive and an Object in a Method

When the program starts, a frame is allocated on the stack for the main() method, and the local variables x and y live there. Calling changePrimitives() creates one more stack frame with its own local variables a and b — copies of the passed values. Modifying a and b has no effect on x and y: they are different memory cells.

The local variable box is created on the stack of main() too, but the Box object itself lives on the heap. What changeObject() receives is a copy of the reference pointing at that same object. So assigning to box.width, box.height and box.depth inside the method changes the object on the heap, and the result is visible back in main():

public class Box {
    double width;
    double height;
    double depth;

    Box(double w, double h, double d) {
        width = w;
        height = h;
        depth = d;
    }
}
public class TestDemo {

    // Changes the state of the object: the caller sees the new values
    static void changeObject(Box box) {
        box.width *= 2;
        box.height /= 2;
        box.depth += 2;
    }

    // Changes copies of the values: the caller sees nothing
    static void changePrimitives(int a, int b) {
        a *= 2;
        b /= 2;
    }

    public static void main(String[] args) {
        Box box = new Box(5, 6, 7);

        int x = 10;
        int y = 10;
        System.out.println("x and y before method invocation: " + x + " " + y);
        changePrimitives(x, y);
        System.out.println("x and y after method invocation: " + x + " " + y);

        System.out.println("box before method invocation: " + box.width + " " + box.height + " " + box.depth);
        changeObject(box);
        System.out.println("box after method invocation: " + box.width + " " + box.height + " " + box.depth);
    }
}

Program output:

x and y before method invocation: 10 10
x and y after method invocation: 10 10
box before method invocation: 5.0 6.0 7.0
box after method invocation: 10.0 3.0 9.0

Where Intuition Breaks Down: Reassigning a Parameter

If objects really were passed by reference, assigning a brand new object to the parameter inside the method would also change the caller's variable. In Java it does not: only the local copy of the reference is reassigned, while the original variable keeps pointing at the old object.

public class ReassignDemo {

    // A new object is assigned to the parameter - nothing changes outside
    static void reassign(Box box) {
        box = new Box(1, 1, 1);
        box.width = 100;
    }

    // The state of the received object is modified - the change is visible outside
    static void mutate(Box box) {
        box.width = 100;
    }

    public static void main(String[] args) {
        Box first = new Box(5, 6, 7);
        reassign(first);
        System.out.println("after reassign: " + first.width);

        Box second = new Box(5, 6, 7);
        mutate(second);
        System.out.println("after mutate: " + second.width);
    }
}

Program output:

after reassign: 5.0
after mutate: 100.0

The table below answers the only question that really matters here: will the calling method see the change?

What was passed What the method did Visible to the caller? Why
int, double, boolean and other primitives Changed the value of the parameter No The value itself was copied into the method
Reference to a mutable object (Box, int[], StringBuilder, ArrayList) Changed a field or an element of the object Yes The copied reference points at the same object on the heap
Reference to an object Assigned a new object to the parameter No Only the local copy of the reference was reassigned
String, Integer and other immutable types "Changed" them with + or by reassignment No The object is immutable, so a new one is created

Arrays, String and StringBuilder

An array in Java is an object as well, so a method that receives an array can change its elements and the caller will see the new values. String, on the other hand, is immutable: the statement text += " Java" inside a method builds a new string and assigns it to the local parameter, leaving the original untouched. If a string has to grow inside a method, use StringBuilder or return the result:

public class ArrayStringDemo {

    static void fill(int[] numbers) {
        numbers[0] = 42;            // an array is an object, so the change is visible outside
    }

    static void addSuffix(String text) {
        text += " Java";            // a new string is created, the original stays the same
    }

    static void addSuffix(StringBuilder text) {
        text.append(" Java");       // the same object is modified
    }

    public static void main(String[] args) {
        int[] numbers = {1, 2, 3};
        fill(numbers);
        System.out.println("numbers[0]: " + numbers[0]);

        String text = "Hello";
        addSuffix(text);
        System.out.println("text: " + text);

        StringBuilder builder = new StringBuilder("Hello");
        addSuffix(builder);
        System.out.println("builder: " + builder);
    }
}

Program output:

numbers[0]: 42
text: Hello
builder: Hello Java

By the way, the two addSuffix() methods with different parameter types are an example of method overloading: the compiler picks the right version from the type of the argument.

Worth knowing

A final parameter (void changeObject(final Box box)) forbids reassigning the parameter, but it does not protect the object: box.width = 100; still compiles and still works. If a method must not touch someone else's data, work on a defensive copy or use immutable types.

Example 2. Returning an Object from a Method

Methods can not only accept objects but also create them and hand them back. What is returned is a reference to the object on the heap — the object itself is never copied. The method incrementByTen() leaves the current object alone and produces a new one with a larger field value:

public class ReturnObjectExample {
    int a;

    ReturnObjectExample(int i) {
        a = i;
    }

    ReturnObjectExample incrementByTen() {
        ReturnObjectExample temp = new ReturnObjectExample(a + 10);
        return temp;
    }

    public static void main(String[] args) {
        ReturnObjectExample ob1 = new ReturnObjectExample(2);
        ReturnObjectExample ob2 = ob1.incrementByTen();
        System.out.println("ob1.a: " + ob1.a);
        System.out.println("ob2.a: " + ob2.a);
    }
}

Program output:

ob1.a: 2
ob2.a: 12

The intermediate variable temp is optional here — the body can be written as a single line: return new ReturnObjectExample(a + 10);. An object created inside a method does not disappear when the method ends: it stays on the heap as long as at least one reference to it exists, and only then becomes eligible for garbage collection.

Key Takeaways

  • Java passes arguments by value only: a primitive's value is copied, an object's reference is copied.
  • Changing the state of an object inside a method is visible to the calling code.
  • Assigning a new object to the parameter is not visible to the calling code.
  • Arrays and collections behave like the mutable objects they are; String, Integer and other immutable types cannot be "changed" through a parameter.
  • A method can create an object and return a reference to it — the object is not copied on return.

Frequently Asked Questions

Is Java pass by value or pass by reference?

Java is strictly pass by value. What gets copied into the method is the value of the reference, that is the address of the object on the heap. The method works with the same object and can change its fields, but it cannot make the caller's variable point at another object.

Why does a swap() method fail to swap two variables?

The method receives copies of the values or copies of the references, so swapping them inside the method only affects the local parameters. To exchange values, put them into an array, an object or a collection and swap the elements of that object, or return the result from the method.

Can a method modify a String that was passed to it?

No. String is immutable, so concatenation inside the method creates a new string and assigns it to the local parameter. Use StringBuilder when the text must be modified inside the method, or return the new string as the result.

How do you return several values from one method in Java?

Java has no out or ref keywords, so you wrap the values in a container: your own class, a record since Java 16, an array or a collection. You can also pass a mutable object in and let the method fill its fields, but returning the result usually reads better.

Comments

Please log in or register to have a possibility to add comment.