Before describing how arguments are passed in Java, it is worth defining how Java variables are allocated in memory. Basically, we're talking about two types of variables: primitives and objects.

Primitive variables are always stored inside the stack memory (the memory space that holds method-specific variables that are short-lived in addition to references to other objects in the heap).

However, objects are stored at two stages. The actual object data is stored inside the heap memory (the memory space that holds objects and JRE classes), and a reference for the object is kept inside stack memory, which just points to the actual object.

1. By Value vs. by Reference

What is meant by "By value" and "By reference":

By value: When arguments are passed by value to a method, it means that a copy of the original variable is being sent to the method and not the original one, so any changes applied inside the method are actually affecting the copy version.

When arguments are passed by value to a method, it means that a copy of the original variable is being sent to the method and not the original one, so any changes applied inside the method are actually affecting the copy version. By reference: When arguments are passed by reference, it means that a reference or a pointer to the original variable is being passed to the method and not the original variable data.

2. How Arguments Are Passed in Java

In Java, arguments are always passed by value regardless of the original variable type. Each time a method is invoked, a copy for each argument is created in the stack memory and the copy version is passed to the method.

If the original variable type is primitive, then simply, a copy of the variable is created inside the stack memory and then passed to the method.

If the original type is not primitive, then a new reference or pointer is created inside the stack memory, which points to the actual object data, and the new reference is then passed to the method, (at this stage, two references are pointing to the same object data).

3. Fixing Some Concerns

In the following example, we try to validate that "Java is always pass-by-value" by passing several argument types (primitive, wrappers, collections, business objects) and checking whether they are modified after the method call.

Passing Primitive Arguments

public static void main(String[] args) { int x = 1; int y = 2; System.out.print("Values of x & y before primitive modification: "); System.out.println(" x = " + x + " ; y = " + y ); modifyPrimitiveTypes(x,y); System.out.print("Values of x & y after primitive modification: "); System.out.println(" x = " + x + " ; y = " + y ); } private static void modifyPrimitiveTypes(int x, int y) { x = 5; y = 10; }





Output:

Values of x & y before primitive modification: x = 1 ; y = 2 Values of x & y after primitive modification: x = 1 ; y = 2





Output Description:

The two variables, x and y, are of primitive types and are thus stored inside the stack memory. When calling modifyPrimitiveTypes(), two copies are created inside the stack memory (let's say w and z) and are then passed to the method. Hence, the original variables are not being sent to the method and any modification inside the method flow is affecting only the copies.

Passing Wrapper Arguments

public static void main(String[] args) { Integer obj1 = new Integer(1); Integer obj2 = new Integer(2); System.out.print("Values of obj1 & obj2 before wrapper modification: "); System.out.println("obj1 = " + obj1.intValue() + " ; obj2 = " + obj2.intValue()); modifyWrappers(obj1, obj2); System.out.print("Values of obj1 & obj2 after wrapper modification: "); System.out.println("obj1 = " + obj1.intValue() + " ; obj2 = " + obj2.intValue()); } private static void modifyWrappers(Integer x, Integer y) { x = new Integer(5); y = new Integer(10); }





Output:

Values of obj1 & obj2 before wrapper modification: obj1 = 1 ; obj2 = 2 Values of obj1 & obj2 after wrapper modification: obj1 = 1 ; obj2 = 2





Output Description:

Wrappers are stored inside the heap memory with a correspondent reference inside the stack memory.

When calling modifyWrappers(), a copy for each reference is created inside the stack memory, and the copies are passed to the method. Any change to the reference inside the method is actually changing the reference of the copies and not the original references.

P.S: If you change the value of wrapper objects inside the method like this: x += 2, the change is not reflected outside the method, since wrapper objects are immutable. They create a new instance each time their state is modified. For more information about immutable classes, check out "How to create an immutable class in Java". String objects work similarly to wrappers, so the above rules apply also on strings.

Passing Collection Arguments

public static void main(String[] args) { List<Integer> lstNums = new ArrayList<Integer>(); lstNums.add(1); System.out.println("Size of list before List modification = " + lstNums.size()); modifyList(lstNums); System.out.println("Size of list after List modification = " + lstNums.size()); } private static void modifyList(List<Integer> lstParam) { lstParam.add(2); }





Output:

Size of list before List modification = 1 Size of list after List modification = 2





Output Description:

When defining an ArrayList or any collection in Java, a reference is created inside the stack that points to multiple objects inside the heap memory. When calling modifyList(), a copy of the reference is created and passed to the method. The actual object data is referenced by two references, and any change done by one reference is reflected in the other.

Inside the method, we called lstParam.add(2), which actually tries to create a new Integer object in the heap memory and link it to the existing list of objects. Hence the original list reference can see the modification, since both references are pointing to the same object in memory.

Passing Business Objects as Arguments

public static void main(String[] args) { Student student = new Student(); System.out.println("Value of name before Student modification = " + student.getName()); modifyStudent(student); System.out.println("Value of name after Student modification = " + student.getName()); } private static void modifyStudent(Student student) { student.setName("Alex"); }





Output:

Value of name before Student modification = null Value of name after Student modification = Alex





Output Description:

The student object is created inside the heap space, and a reference for it is defined inside the stack. When calling modifyStudent(), a copy of the reference is created inside the stack and passed to the method. Any modifications to the object attributes inside the method are reflected in the original reference.

Conclusion

In Java, arguments are always passed by value. The copy would be either a reference or a variable depending on the original variable type. From now on, you can use the following tips in order to understand how modifying arguments inside methods affects the original variable: