Java • Functions and Program Structure

Pass-by-Value & Object References — Use pass-by-value semantics and understand object reference passing (conceptual)

Understand how Java copies values when calling methods, and why objects seem to behave differently from primitives.

Java programming emblem with a coffee cup, code braces, and terminal
Inside this lesson 10 sections 5 questions 4 figures

SECTION 1

Historical Context & Motivation

When you hand a friend a copy of your essay, they can scribble all over it without changing your original. But when you hand them a link to a shared Google Doc, any edits they make are visible to you too. Programming languages face a similar design decision every time a function is called: should the function receive a copy of the data, or should it receive direct access to the original? This question, known as the parameter-passing mechanism, has shaped how every major language works.

1958 • ALGOL & Call-by-Value / Call-by-Name

The ALGOL 58 and ALGOL 60 languages formalized two strategies: call-by-value (copy the data) and call-by-name (re-evaluate the expression each time). These ideas became the foundation for all later parameter-passing debates.

1972 • C Adopts Pass-by-Value with Pointers

The C language chose strict pass-by-value, but allowed programmers to pass pointers—memory addresses—by value. This gave the effect of pass-by-reference while technically still copying a value.

1995 • Java Chooses Pass-by-Value Only

James Gosling designed Java to be simpler and safer than C++. Java adopted strict pass-by-value for everything—primitives and object references alike—eliminating pointer arithmetic and the bugs that came with it.

2000s • The Great "Pass-by-Reference" Debate

Because modifying an object inside a method is visible to the caller, countless online debates erupted over whether Java is "really" pass-by-value. The answer is definitively yes—but the subtlety continues to trip up learners today.

So why does this matter to you as a Java programmer? Understanding pass-by-value semantics answers a critical question: when I pass data to a method, can that method permanently change my original data? The answer depends on whether you are passing a primitive or an object reference, and getting it wrong is one of the most common sources of bugs in Java programs.

SECTION 2

Core Principles & Definitions

Before diving into examples, you need to lock down a few definitions. Java has exactly one parameter-passing rule, but the consequences differ depending on the type of data you pass.

1

Pass-by-Value

When a method is called, Java copies the value stored in each argument variable and places that copy into the parameter variable. The method works with the copy, never the original variable.
2

Primitive Types

Types like int, double, boolean, and char store the actual data directly. Copying a primitive means the method gets its own independent number or character.
3

Object References

Variables of class types (like String, int[], or any custom class) do not store the object itself. They store a reference—essentially an address pointing to the object on the heap.
4

Copying a Reference ≠ Copying an Object

When Java passes an object reference by value, it copies the reference (the address), not the object. Both the original and the copy now point to the same object in memory.
5

Reassignment vs. Mutation

Reassigning a parameter to a new object changes only the local copy of the reference—the caller's variable is unaffected. Mutating the object (changing its fields) is visible to the caller because both references point to the same object.
✦

KEY TAKEAWAY

Think of an object reference like a slip of paper with a house address written on it. Pass-by-value means Java photocopies that slip of paper and hands the copy to the method. Now both slips have the same address, so either one can walk to that house and rearrange the furniture (mutation). But if the method crosses out the address on its copy and writes a new one (reassignment), your original slip still points to the same house—nothing changes for you.

SECTION 3

Visual Explanation — Memory Diagrams

The diagram below shows what happens in memory when a method receives a primitive versus an object reference. Study the two tracks carefully—the left track shows a primitive int being passed, and the right track shows an array reference being passed. The key insight is that both tracks copy a value, but what that value represents is fundamentally different.

Pass-by-Value: Primitives vs. Object ReferencesPRIMITIVE (int)OBJECT REFERENCE (int[])CALLERx42← stores actual valuearr@A1← stores referenceHeap Object @A1[10, 20, 30]⬇ COPY value into parameter⬇ COPY reference into parameterMETHODparam42← independent copyparam@A1← copy of referenceMethod changes param to 99Method does param[0] = 99param99x (caller)42 ✓ unchangedHeap Object @A1[99, 20, 30]arr (caller)@A1 → sees [99,20,30]Original x is UNCHANGEDOriginal arr sees MUTATED object
Left track: a primitive int is copied, so the method's change to param has no effect on the caller's x. Right track: the reference @A1 is copied, but both copies point to the same heap object. Mutating the object through param[0] = 99 is visible to the caller.

Notice that in both cases, Java performed the exact same operation: it copied the value stored in the variable. For the primitive, that value was the number 42. For the array, that value was the reference @A1 (the address of the array on the heap). The rule is uniform—Java is always pass-by-value. The consequence differs because references share the underlying object while primitive copies are completely independent.

SECTION 4

How It Works — The Stack and the Heap

To truly understand pass-by-value, you need a mental model of Java's two main memory areas. The stack is where local variables and method parameters live—each method call gets its own stack frame. The heap is a shared pool of memory where objects (arrays, Strings, custom class instances) actually live. A reference variable on the stack holds an address that points into the heap.

What Happens Step by Step

  1. Caller's frame is active. The caller's local variables sit on the stack. If a variable is a reference type, the actual object is on the heap and the stack variable stores only the address.
  2. Method call begins. Java pushes a new stack frame for the called method. For each argument, Java copies the value from the caller's variable into the corresponding parameter in the new frame.
  3. Method executes. The method works with its own copies. It can modify its parameter variables freely—but since they are copies, the caller's variables are untouched.
  4. However—if the copied value is a reference, the method can follow that reference to the heap and mutate the shared object. The caller will see these changes because both references point to the same place.
  5. Method returns. The method's stack frame is destroyed. All parameter copies vanish. The caller's variables remain exactly as they were (for primitives) or still point to the same—possibly mutated—object (for references).

The Reassignment Trap

One of the trickiest points is this: inside a method, if you write param = new SomeObject(), you are only changing the method's copy of the reference to point to a brand-new object. The caller's variable still points to the original object. This is the single most convincing proof that Java is pass-by-value, not pass-by-reference. If Java were truly pass-by-reference, reassigning the parameter inside the method would also change the caller's variable—but it doesn't.

⚠

Common Misconception

Many students say "Java passes objects by reference." This is incorrect. Java passes object references by value. There is a big difference: pass-by-reference would let a method make the caller's variable point to a different object, which Java cannot do.

SECTION 5

Detailed Breakdown — Primitives, Objects & Strings

Let's categorize the behavior you'll encounter with the three most common data categories: primitives, mutable objects, and immutable objects (like Strings). Each follows the same pass-by-value rule, but the observable behavior can feel different.

Three Categories of Pass-by-Value BehaviorPRIMITIVESint, double, boolean, charValue IS the data.Copy = independent clone.Method CANNOT affect caller.void add(int n) {n++;}After call:x is still 5 ✓🛡️SAFE — always isolatedMUTABLE OBJECTSarrays, ArrayList, custom classesValue IS a reference (address).Copy = 2nd pointer to same obj.Method CAN mutate the object.void set(int[] a){a[0]=9;}After call:arr[0] is now 9 ⚠️🔗SHARED — mutation visibleIMMUTABLE OBJECTSString, Integer, wrapper classesValue IS a reference (address).Object cannot be changed."Change" creates a new object.void up(String s){s+="!"}After call:str is still "hi" ✓🔒SAFE — no mutation possibleReassign? No effect on callerMutate? Not applicableReassign? No effect on callerMutate? YES, visible to callerReassign? No effect on callerMutate? Impossible (immutable)
All three columns follow pass-by-value. The difference lies in what the "value" represents and whether the object on the heap can be changed. Reassignment never affects the caller in any case.

The String class is a special case worth emphasizing. Even though String is a reference type, it is immutable—its internal character data cannot be changed after creation. When you write s = s + "!" inside a method, you are creating a brand-new String object and reassigning the local parameter to it. The caller's variable still refers to the original String. This is why Strings "behave like primitives" even though they are technically objects.

SECTION 6

Worked Example — Tracing Through Code

Let's trace through a complete Java program step by step to predict the output. This exercise combines primitives, mutable objects, and reassignment—all the concepts we've discussed.

ℹ

Code to Trace

public class Demo { static void change(int num, int[] arr, String msg) { num = 100; arr[0] = 100; arr = new int[]{999}; msg = msg + " world"; } public static void main(String[] args) { int x = 5; int[] data = {10, 20}; String greeting = "hello"; change(x, data, greeting); System.out.println(x); System.out.println(data[0] + ", " + data[1]); System.out.println(greeting); } }

Tracing the change() Method Call

  1. 1

    Step 1 — Before the Call

    In main, three variables exist on the stack: x = 5 (primitive), data holds a reference to the heap array {10, 20}, and greeting holds a reference to the String "hello" on the heap.
  2. 2

    Step 2 — Values Are Copied into Parameters

    Java copies 5 into num, copies the reference to the array into arr, and copies the reference to "hello" into msg. At this point, arr and data both point to the same array object.
  3. 3

    Step 3 — num = 100 (primitive reassignment)

    This changes the local copy num to 100. The caller's x is completely unaffected because it's a separate copy.
    x in main is still 5
  4. 4

    Step 4 — arr[0] = 100 (object mutation)

    The method follows the reference stored in arr to the heap array and changes its first element from 10 to 100. Since data in main points to the same array, the caller will see this change.
    data in main now refers to {100, 20}
  5. 5

    Step 5 — arr = new int[]{999} (reference reassignment)

    This creates a brand-new array on the heap and makes the local parameter arr point to it. Crucially, data in main still points to the original (now mutated) array. The reassignment only affected the method's copy of the reference.
    data in main still points to {100, 20} — not {999}
  6. 6

    Step 6 — msg = msg + " world" (String reassignment)

    String concatenation creates a new String object "hello world" on the heap and reassigns msg to point to it. The caller's greeting still refers to the original "hello" because Strings are immutable and the reference was only locally reassigned.
    greeting in main is still "hello"
  7. 7

    Step 7 — Final Output

    After change() returns, the three println calls produce:
    5 → 100, 20 → hello

SECTION 7

Pass-by-Value vs. Pass-by-Reference — How Other Languages Compare

Java's pass-by-value-only approach is a deliberate design choice. Other languages handle parameter passing differently. Understanding these differences helps clarify why Java works the way it does and prevents confusion when you learn additional languages.

FeatureJava (Pass-by-Value)C++ (Has Pass-by-Reference)
PrimitivesCopied. Method cannot affect the caller's variable.By default, copied (same as Java). With &, passed by reference.
ObjectsReference is copied. Method can mutate the object but cannot make the caller's variable point elsewhere.With &, method CAN make the caller's variable point to a different object entirely.
Reassignment visible to caller?NeverYes, when & is used
Swap function possible?Cannot write a simple swap(a, b) that swaps the caller's variables.Easy with swap(int &a, int &b).
SafetySimpler mental model; fewer surprise side effects.More power but more potential for accidental aliasing bugs.

Java vs. C++ parameter-passing behavior

✦

KEY TAKEAWAY

The classic test for true pass-by-reference is the swap test: can you write a method that swaps two variables in the caller? In Java, you cannot—the method only swaps its local copies of the references. This confirms that Java is strictly pass-by-value.

SECTION 8

Connection to Advanced Concepts

Understanding pass-by-value and object references sets the stage for several advanced topics you'll encounter as you progress in computer science. The table below maps what you've learned to where it leads.

What You Know NowAdvanced TopicWhy It Matters
Objects live on the heapGarbage CollectionWhen no references point to a heap object, Java's garbage collector reclaims that memory automatically.
References can be sharedAliasing & Thread SafetyIf two threads hold references to the same mutable object, you need synchronization to avoid race conditions.
Strings are immutableImmutable Design PatternDesigning objects that cannot be changed after creation is a powerful strategy for building safe, predictable code.
Methods can mutate shared objectsDefensive CopyingTo protect an object from unwanted mutation, you can pass a deep copy instead of the original reference.

From pass-by-value fundamentals to advanced Java topics

As you move into AP Computer Science A or college-level coursework, you'll see these concepts applied in data structures (linked lists, trees) and object-oriented design patterns. Every time you pass an object to a method, the question of who owns the data and who can change it becomes central to writing correct, maintainable code. The mental model you've built in this lesson—stack frames, heap objects, copied references—will serve you throughout your programming career.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

A student claims: "Java passes objects by reference because when I change an object's field inside a method, the caller sees the change." Explain why this reasoning is flawed and state what Java actually does.
Show answer
The student is confusing "passing a reference by value" with "pass-by-reference." Java copies the reference (the address of the object) into the method's parameter. Both the caller's variable and the parameter point to the same object, so mutations are visible. However, if the method reassigns the parameter to a new object, the caller's variable is unaffected—this would not be the case in true pass-by-reference. The correct statement is: Java is always pass-by-value, but for object types, the value being passed is a reference.

Question 2: BASIC CALCULATION

What does the following code print? int a = 10; increment(a); System.out.println(a); where static void increment(int n) { n = n + 5; }. Explain your reasoning.
Show answer
The output is 10. Because int is a primitive type, Java copies the value 10 into the parameter n. Inside the method, n becomes 15, but this is a separate variable from a. When the method returns, a is still 10. The modification to n is lost when the method's stack frame is destroyed.

Question 3: INTERMEDIATE

Predict the output of this code: int[] nums = {1, 2, 3}; replace(nums); System.out.println(nums[0]); static void replace(int[] arr) { arr[0] = 99; arr = new int[]{-1, -2, -3}; arr[0] = 77; } Explain each line of the method.
Show answer
The output is 99. Line by line: arr[0] = 99 mutates the original array (the one nums points to), changing its first element. Then arr = new int[]{-1, -2, -3} reassigns the local parameter arr to point to a brand-new array—this does NOT affect nums. Finally, arr[0] = 77 modifies the new array, which nums has no connection to. The original array remains {99, 2, 3}.

Question 4: APPLIED

You want to write a method that "clears" a student's name by setting it to an empty string. You try: static void clearName(String name) { name = ""; } but after calling clearName(studentName), studentName is unchanged. (a) Explain why. (b) Propose a design that actually works.
Show answer
(a) String is immutable and the reference is passed by value. Inside clearName, name = "" reassigns only the local copy of the reference to point to the empty-string object. The caller's studentName still points to the original String. (b) One solution is to wrap the name in a mutable object, such as a Student class with a setName() method. Pass the Student object to the method and call student.setName(""). Since the method shares the reference to the same Student object, the mutation is visible to the caller. Alternatively, have the method return the new String and let the caller assign it.

Question 5: CRITICAL THINKING

Explain why it is impossible to write a working swap(Object a, Object b) method in Java that swaps the caller's two variables. Connect your explanation to Java's parameter-passing mechanism and contrast with what would be possible if Java supported true pass-by-reference.
Show answer
In Java, swap(a, b) receives copies of the references stored in the caller's variables. Inside the method, you can swap the local copies—making one local variable point to the other's object and vice versa—but this has zero effect on the caller's variables, which still hold their original references. For a swap to work, the method would need to directly modify the caller's variable storage, which requires pass-by-reference (like C++'s & parameters). In C++, swap(int &a, int &b) gives the method an alias to the caller's actual variables, so assignments inside the method change the caller's variables directly. Java's pass-by-value design intentionally prevents this, trading some flexibility for a simpler and safer mental model.

SUMMARY

Lesson Summary

Summary

Java uses pass-by-value for every method call—no exceptions. For primitive types like int and double, the actual data value is copied, making the method's parameter completely independent from the caller's variable. For object types, the reference (memory address) is copied, which means both the caller and the method point to the same object on the heap. This allows the method to mutate the shared object, but it cannot change which object the caller's variable points to.

The critical distinction is between mutation (changing an object's contents through a shared reference, which IS visible to the caller) and reassignment (making the parameter point to a different object, which is NOT visible to the caller). Immutable objects like String effectively behave like primitives because they cannot be mutated—any "change" creates a new object and is only a local reassignment. Remember: Java passes object references by value, not objects by reference.

Varsity Tutors • Java • Pass-by-Value & Object References