Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 8

Java's Pass-by-Value Mechanism

Recommended resources

Hello! Welcome to the final lesson of our "Core Java & JVM Internals" module.

In our last lesson, we explored primitives and wrapper classes, establishing a crucial foundation: primitive variables hold their actual values directly, while object variables hold a reference—a value that points to the object's location in memory.

Today, we'll build directly on that foundation to answer a classic and frequently misunderstood Java interview question: Is Java pass-by-value or pass-by-reference? This lesson will help you explain definitively why Java is always pass-by-value and demonstrate it with clear examples for both primitive and object types. Mastering this will allow you to analyze code behavior accurately and articulate your understanding of Java's memory model with confidence.

1. The Definitive Answer

Let's get straight to the point, as this is a common source of confusion for many developers.

Java is always, 100% of the time, pass-by-value.

The confusion arises because the behavior appears to be "pass-by-reference" when you work with objects. The key is understanding what "value" is being passed.

  • Pass-by-value: A copy of the variable's value is passed to the method. Changes made to the parameter inside the method do not affect the original variable outside the method.
  • Pass-by-reference: An alias or reference to the original variable itself is passed to the method. Changes made to the parameter inside the method do affect the original variable.

Java uses the first approach exclusively.


2. The Two Scenarios: Primitives and Objects

To truly understand this, we need to see it in action. The following video provides an excellent and concise explanation of how pass-by-value works for both primitives and objects.

Java is ALWAYS Pass By Value. Here's Why

Watch this video from 'Coding with John'. It clearly explains the core concept and uses a simple, effective example to demonstrate why Java is always pass-by-value, even when it looks like it's not.

Watch the video from 00:32 to 05:07. Pay close attention to the two distinct scenarios: Modifying an object's state (e.g., cheese.setLevelOfStinkiness(...)). Reassigning the object reference inside the method (e.g., cheese = new Cheese(...)). Understanding the difference between these two actions is the key to mastering this topic.


3. Deconstructing Pass-by-Value

Let's break down the concepts from the video with code examples and memory diagrams to solidify your understanding.

Case 1: Passing Primitive Types

This is the most straightforward case. When you pass a primitive type like an int to a method, a copy of its value is made.

public class PrimitiveExample {

    public static void main(String[] args) {
        int originalValue = 10;
        System.out.println("Before calling modify: " + originalValue); // Prints 10

        modify(originalValue);

        System.out.println("After calling modify: " + originalValue);  // Still prints 10
    }

    public static void modify(int copiedValue) {
        // 'copiedValue' is a copy of 'originalValue'.
        // This modification only affects the copy.
        copiedValue = 100;
    }
}

What happens in memory:

  1. A variable originalValue is created in the main method's stack frame with the value 10.
  2. When modify() is called, a new stack frame is created for it. A new variable, copiedValue, is created within this new stack frame.
  3. The value of originalValue (10) is copied into copiedValue.
  4. Inside modify(), copiedValue is changed to 100. This only affects the variable on the modify() method's stack frame.
  5. When modify() finishes, its stack frame is destroyed, and copiedValue disappears. The originalValue in main was never touched.

Case 2: Passing Object References

This is where the confusion starts, but the rule remains the same. When you pass an object, the "value" being passed is the reference (the memory address) to that object. The method gets a copy of this reference.

Let's look at the two distinct scenarios again.

Scenario A: Modifying the Object's State

Since the original reference and the copied reference both point to the exact same object on the heap, you can use the copied reference to change the object's internal state, and the change will be visible outside the method.

class Balloon {
    private String color;

    public Balloon(String color) { this.color = color; }
    public String getColor() { return color; }
    public void setColor(String color) { this.color = color; }
}

public class ObjectExample {
    public static void main(String[] args) {
        Balloon myBalloon = new Balloon("Blue");
        System.out.println("Before: " + myBalloon.getColor()); // Prints "Blue"

        changeColor(myBalloon);

        System.out.println("After: " + myBalloon.getColor());   // Prints "Red"
    }

    public static void changeColor(Balloon balloonParameter) {
        // balloonParameter is a COPY of the reference.
        // It points to the SAME object as myBalloon.
        balloonParameter.setColor("Red");
    }
}
Java Pass-by-Value with Object References
This diagram illustrates exactly what is happening. The `process` method (like our `changeColor` method) receives a copy of the reference. Both the original variable (`someFoo`) and the method parameter (`foo`) point to the same object in memory, allowing the method to modify the object's state.

Scenario B: Reassigning the Reference inside the Method

This is the ultimate proof of pass-by-value. If you reassign the method's parameter to a new object, you are only changing the local copy of the reference. The original reference in the calling method remains unchanged.

public class ObjectExample {
    public static void main(String[] args) {
        Balloon myBalloon = new Balloon("Blue");
        System.out.println("Before: " + myBalloon.getColor()); // "Blue"

        tryToReassign(myBalloon);

        System.out.println("After: " + myBalloon.getColor());   // Still "Blue"
    }

    public static void tryToReassign(Balloon balloonParameter) {
        // balloonParameter now points to a completely new object.
        // The original 'myBalloon' reference is unaffected.
        balloonParameter = new Balloon("Green");
        
        // This change is only visible inside this method.
        System.out.println("Inside method: " + balloonParameter.getColor()); // "Green"
    }
}

The variable myBalloon in main never stops pointing to the original "Blue" balloon object.

For a final, comprehensive read on this, the Baeldung article is an excellent resource.

Pass-By-Value as a Parameter Passing Mechanism in Java

This article, 'Pass-By-Value as a Parameter Passing Mechanism in Java' from Baeldung, provides a clear, authoritative explanation with diagrams that reinforce these concepts.

Read sections 3 ('Parameter Passing in Java'), 3.1 ('Passing Primitive Types'), and 3.2 ('Passing Object References'). This will give you a solid textual summary of everything we've discussed.

Test your understanding!

An interviewer shows you the following code snippet, a classic test of this concept. What will be printed to the console? Explain your reasoning step-by-step as you would in an interview.

class Dog {
    String name;
    Dog(String name) { this.name = name; }
}

public class Main {
    public static void main(String[] args) {
        Dog dogA = new Dog("Max");
        Dog dogB = new Dog("Lucy");

        swapDogs(dogA, dogB);
        System.out.println("1. dogA name: " + dogA.name);
        System.out.println("2. dogB name: " + dogB.name);

        changeName(dogA);
        System.out.println("3. dogA name after changeName: " + dogA.name);
    }

    // Attempts to swap the objects
    public static void swapDogs(Dog d1, Dog d2) {
        Dog temp = d1;
        d1 = d2;
        d2 = temp;
    }

    // Attempts to change the name and reassign the object
    public static void changeName(Dog dog) {
        dog.name = "Charlie";
        dog = new Dog("Buddy");
        dog.name = "Rocky";
    }
}
Show answer

Predicted Output:

1. dogA name: Max
2. dogB name: Lucy
3. dogA name after changeName: Charlie

Explanation:
This code demonstrates the core principles of Java's pass-by-value mechanism.

  1. swapDogs(dogA, dogB) call:

    • The swapDogs method receives copies of the references from dogA and dogB. Let's say dogA's reference is 0x100 (pointing to "Max") and dogB's is 0x200 (pointing to "Lucy"). The method parameters d1 and d2 get the values 0x100 and 0x200.
    • Inside the method, the values of d1 and d2 are swapped. So, d1 becomes 0x200 and d2 becomes 0x100.
    • However, this swap only affects the local copies within the swapDogs method. The original variables dogA and dogB in main are completely unchanged. They still hold 0x100 and 0x200, respectively.
    • Therefore, the first two print statements will show "Max" and "Lucy".
  2. changeName(dogA) call:

    • The changeName method receives a copy of the reference from dogA (value 0x100). The parameter dog now also holds the value 0x100.
    • dog.name = "Charlie";: This line uses the reference 0x100 to find the object on the heap and changes its name field to "Charlie". Since dogA in main also points to this same object, this change is permanent and visible outside the method.
    • dog = new Dog("Buddy");: This is the crucial step. A new Dog object ("Buddy") is created at a new memory address (e.g., 0x300). The local parameter dog is then reassigned to hold this new reference 0x300. This has no effect on the dogA variable in main, which still holds 0x100.
    • dog.name = "Rocky";: This changes the name of the new object at 0x300 to "Rocky". This is irrelevant to the main method's variables.
    • Therefore, the final print statement for dogA.name will show "Charlie".

Conclusion

This lesson concludes our first module on Core Java & JVM Internals. You've now solidified your understanding of some of the most fundamental and interview-critical aspects of the Java language.

Key Takeaways:

  • Java is strictly pass-by-value. There are no exceptions.
  • When passing a primitive type, a copy of the value is passed.
  • When passing an object, a copy of the reference value (the memory address) is passed.
  • Because the method receives a copy of the reference, you can modify the state of the object it points to, but you cannot change the original reference variable to point to a new object.

In our next lesson, we will begin the "Java Collections & Problem-Solving" module. We'll start by diving deep into one of the most important data structures in a Java developer's toolkit: the internal implementation of HashMap. The concepts of equals() and hashCode() you learned earlier will be absolutely essential.