Java Pass By Value and Pass By Reference

Learn java pass by value and pass by reference with practical examples, diagrams, and best practices. Covers java, hashmap, pass-by-reference development techniques with visual explanations.

Java Pass By Value and Pass By Reference: Understanding Object Handling

Hero image for Java Pass By Value and Pass By Reference

Explore the fundamental concepts of pass by value and pass by reference in Java, clarifying how primitive types and objects are handled during method calls.

One of the most common sources of confusion for new Java developers is understanding how arguments are passed to methods. Java is often said to be "pass by value," but this statement can be misleading when dealing with objects. This article will demystify these concepts, explaining how Java handles both primitive types and object references during method invocations, and how this impacts your code's behavior.

The Core Principle: Java is Always Pass By Value

At its heart, Java is strictly pass by value. This means that when you pass an argument to a method, a copy of that argument's value is made and given to the method's parameter. The crucial distinction lies in what constitutes the "value" being copied:

  • For primitive types (e.g., int, char, boolean, double): The actual value of the primitive is copied. Changes to the parameter inside the method do not affect the original variable outside the method.
  • For objects: The value being copied is the reference to the object, not the object itself. This reference is essentially an address in memory where the object resides. So, while the reference is passed by value (a copy of the reference is made), both the original reference and the copied reference point to the same object in memory. This allows the method to modify the object's state through the copied reference, which will be visible outside the method.
flowchart TD
    A[Caller Method] --> B{Call `modifyValue(int x)`};
    B --> C[Copy of 'a' (value 10) assigned to 'x'];
    C --> D{Inside `modifyValue`};
    D --> E[x = 20 (original 'a' unchanged)];
    E --> F[Return];
    F --> G[Caller Method (a is still 10)];

    H[Caller Method] --> I{Call `modifyObject(MyObject obj)`};
    I --> J[Copy of 'myObj' reference assigned to 'obj'];
    J --> K{Inside `modifyObject`};
    K --> L[obj.setValue(50) (modifies original object)];
    L --> M[Return];
    M --> N[Caller Method (myObj.value is now 50)];

Flowchart illustrating pass by value for primitives and object references

Pass By Value with Primitive Types

When you pass a primitive type to a method, the method receives a completely independent copy of that value. Any operations performed on the parameter within the method will not affect the original variable in the calling scope. This is straightforward and behaves exactly as one would expect from a "pass by value" mechanism.

public class PrimitiveExample {
    public static void main(String[] args) {
        int a = 10;
        System.out.println("Before method call: a = " + a); // Output: 10
        modifyPrimitive(a);
        System.out.println("After method call: a = " + a);  // Output: 10 (unchanged)
    }

    public static void modifyPrimitive(int x) {
        x = 20; // This changes the local copy 'x', not the original 'a'
        System.out.println("Inside method: x = " + x); // Output: 20
    }
}

Demonstrating pass by value with a primitive int.

Pass By Value with Object References

This is where the confusion often arises. When an object is passed to a method, a copy of the reference to that object is passed. Both the original variable and the method's parameter now hold references pointing to the same object in memory. This means:

  1. Modifying the object's state: If you use the method's parameter to change the object's fields (e.g., obj.setValue(50)), these changes will be reflected in the original object because both references point to it.
  2. Reassigning the parameter: If you reassign the method's parameter to point to a new object (e.g., obj = new MyObject();), this change only affects the local parameter. The original variable outside the method will still point to the original object.
class MyObject {
    int value;

    public MyObject(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

public class ObjectReferenceExample {
    public static void main(String[] args) {
        MyObject myObj = new MyObject(100);
        System.out.println("Before method call: myObj.value = " + myObj.getValue()); // Output: 100

        modifyObject(myObj);
        System.out.println("After method call (state modified): myObj.value = " + myObj.getValue()); // Output: 200

        reassignObject(myObj);
        System.out.println("After method call (reassigned parameter): myObj.value = " + myObj.getValue()); // Output: 200 (original object unchanged)
    }

    public static void modifyObject(MyObject obj) {
        obj.setValue(200); // Modifies the state of the object pointed to by 'myObj'
        System.out.println("Inside modifyObject: obj.value = " + obj.getValue()); // Output: 200
    }

    public static void reassignObject(MyObject obj) {
        obj = new MyObject(300); // 'obj' now points to a NEW object. 'myObj' still points to the original.
        System.out.println("Inside reassignObject: obj.value = " + obj.getValue()); // Output: 300
    }
}

Demonstrating pass by value with object references: modifying state vs. reassigning the reference.

Special Case: Strings and Immutable Objects

Java's String class and other immutable objects (like Integer, Long, etc.) behave somewhat differently, which can add another layer of confusion. While String references are still passed by value, String objects themselves cannot be modified after creation. Any operation that appears to modify a String (e.g., concatenation, toUpperCase()) actually creates a new String object and returns a reference to it.

This means that if you pass a String to a method and then perform an operation that "changes" it, you are actually creating a new String and reassigning the local parameter to point to this new String. The original String variable outside the method remains unchanged, similar to reassigning any other object reference.

public class StringExample {
    public static void main(String[] args) {
        String message = "Hello";
        System.out.println("Before method call: message = " + message); // Output: Hello
        modifyString(message);
        System.out.println("After method call: message = " + message);  // Output: Hello (unchanged)
    }

    public static void modifyString(String s) {
        s = s + " World"; // Creates a NEW String object, 's' now points to it
        System.out.println("Inside method: s = " + s); // Output: Hello World
    }
}

Demonstrating String immutability and pass by value.