Java Method Parameters and Pass-by-Value
Understand exactly what a method receives and which changes its caller can observe. Trace copied values, shared array contents, and deliberate replacement results without relying on misleading pass-by-reference shortcuts.
Before you start: complete Methods and Return Values and review array references and copies. All source examples run with Java 21. Predict both the helper's view and the caller's view before pressing Run.
What you will be able to do
- Match argument positions and compatible types to declared parameters.
- Explain that every Java parameter receives a copied value.
- Distinguish changing a primitive parameter from assigning a returned result in the caller.
- Trace mutation through a copied array reference separately from reference reassignment.
- Recognize aliases, immutable strings, and the limits of final parameters.
- Choose and test an explicit in-place or copy-returning method contract.
Parameters are named inputs; arguments supply their values
A parameter is declared in the method header, such as int planned. An argument is an expression supplied at a call, such as remainingMinutes(60, 40). The first argument supplies the first parameter, the second supplies the second, and so on. The caller's variable names do not rearrange that mapping.
In the first call, planned receives 60 and completed receives 40, producing 20. In the reversed call, the helper receives 40 and 60 and returns zero under its floor-at-zero policy. Both calls compile because their arguments are ints, but only the first order represents the intended question.
Java checks the argument count and compatible types, not the business meaning of each value. These ordinary method calls use positional arguments, not named arguments or default parameter values. Use meaningful names and intermediate variables when several same-typed inputs could easily be reversed. Both durations here obey the stated 0..1440 contract.
Main.java · Argument position carries meaning
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Correct order: 20
Reversed order: 0
Every parameter starts with a copied value
Java always passes by value. For a primitive argument such as an int, the parameter receives a copy of the numeric value. In addFiveLocally, the caller's minutes variable and the helper's minutes parameter are distinct variables even though they use the same spelling and initially contain 10.
The helper adds five to its own parameter, so its printed value becomes 15. When control returns, main's variable still contains 10. The method did not receive access to that caller variable's assignment slot. Calling the parameter something else would not change this behavior.
Think of each call as establishing fresh local input variables. A parameter assignment changes one of those variables. It does not automatically travel back into the caller. The numeric examples use small values intentionally; passing by value does not remove arithmetic overflow limits or validate a method's domain.
Main.java · Reassigning a primitive parameter is local
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Parameter minutes: 15
Caller minutes: 10
Trace the two variables called minutes
- main has its own minutes variable containing 10.
- The helper starts with a separate parameter containing a copied 10.
- The helper assigns 15 to its parameter and prints 15.
- The caller's variable was never assigned, so main still prints 10.
Rename the helper parameter to amount. The behavior stays the same because variable spelling does not determine sharing.
Use a result when the caller should adopt a new value
withFiveMore calculates and returns a replacement int. The first call deliberately discards it, which Java permits. The caller's minutes remains 10 because nothing assigns the returned result to that variable. A successful method calculation and an updated caller variable are separate events.
The second call appears on the right side of minutes = withFiveMore(minutes). The helper receives 10 and returns 15, then main performs its own assignment. That assignment is why the caller changes. The helper still did not receive a reference to main's primitive variable.
Use this pattern for a transformed value: calculate, return, and assign at the call site when the change is wanted. It makes ownership of the update visible. Printing the replacement or returning it without using it does not perform the caller's assignment on your behalf.
Main.java · Save a returned replacement deliberately
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
After discarded result: 10
After saved result: 15
For an array argument, the copied value is a reference
An array variable contains a reference to an array object. Passing scores copies that reference value into the values parameter. It does not clone the array, and it does not give the helper the caller variable itself. Initially, the caller variable and the parameter both identify the same array object.
values[0] = 99 follows the copied reference and edits that shared object's first element. The caller later follows its own reference to the same object and sees 99. This is shared mutation through copied references, not an exception to Java's pass-by-value rule.
Separate two questions: which variable is being assigned, and which object is being accessed? An element assignment changes storage inside the referenced array. A parameter assignment changes only the local parameter variable. That distinction explains why the next example behaves differently without introducing a second parameter-passing mechanism.
Main.java · A copied reference can reach shared storage
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Caller first score: 99
Assigning a new array to a parameter is local rebinding
replaceLocally assigns a new array reference to its values parameter. After that assignment, values identifies the new array while main's scores still identifies the original. The local replacement prints [99, 100], but the caller's values remain [40, 70].
The caller reference was not overwritten because the helper received only its value, copied into another variable. Reassigning that local variable cannot redirect the original one. Assigning values = null would follow the same rule: it would not make main's scores variable null.
If the goal is to replace the caller's array, return a new array and let the caller explicitly assign it. If the goal is to edit existing elements, document that mutation instead. A vague name such as fixArray can conceal which contract is intended, even when both implementations compile.
Main.java · Replacing the parameter does not replace the caller variable
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Local replacement: [99, 100]
Caller scores: [40, 70]
Trace each assignment before predicting the final array
The mixed example begins with both references identifying the original [40, 70] array. values[0] = 41 mutates that shared storage. The following values = new int[] {90, 95} changes only the parameter's binding, so the caller continues to identify the now-modified original.
The final values[0] = 100 edits the new array. The helper prints [100, 95], while the caller prints [41, 70]. Returning from the helper does not undo the earlier shared mutation. Rebinding after a mutation cannot retroactively turn that mutation into an independent copy operation.
For each line, name the current object before following its index. You do not need a real memory address: labels such as original array and replacement array are enough. Confusing the variable's name with the object's identity is what makes this otherwise small trace seem mysterious.
Main.java · Separate mutation from later rebinding
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Helper scores: [100, 95]
Caller scores: [41, 70]
Trace the original and replacement arrays
- Caller scores and parameter values initially identify the original [40, 70].
- The element assignment changes the original to [41, 70].
- The parameter is rebound to a different [90, 95] array.
- Only that replacement becomes [100, 95]; the caller still identifies [41, 70].
Remove the first element assignment and predict why the caller would instead keep [40, 70].
Strings do not introduce another passing rule
A String parameter also receives a copied reference value. String objects are immutable, so strip and toUpperCase produce a result without editing the original text object. Assigning that result to the local name parameter does not assign it to the caller's name variable.
The brackets in the output make retained whitespace visible. normalizeLocally prints MINA inside the helper, but main still has its original text with surrounding spaces. normalizedName returns the transformed text, and the caller's explicit assignment adopts it.
Do not conclude that String is passed by value while arrays are passed by reference. Both reference values are copied. The difference here is what operations the referenced object supports: an array element can be mutated, while a String's text cannot. Other immutable and mutable reference types will follow the same underlying argument rule.
Main.java · String reassignment follows the same value rule
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Local name: [MINA]
Caller name: [ Mina ]
Saved name: [MINA]
Different parameter names may still reach one object
adjustBoth receives two array parameters. Passing shared twice gives both parameters copies of the same reference. first[0] adds one, changing 10 to 11, then second[0] adds ten to that same element, producing 21. Two parameters do not guarantee two arrays.
With separately created arrays, the first mutation changes 10 to 11 in one object, while the second changes 20 to 30 in another. The helper's code is identical; the relationship among the supplied references changes the result. An alias is another reference to the same object, not an automatic copy of its contents.
When a mutating method accepts several reference inputs, decide whether aliasing is allowed and test it. For a non-mutating transformation, make an independent copy at the appropriate depth instead. A one-dimensional int[] copy separates its primitive elements; a copied int[][] outer array can still share rows, as the arrays lesson explained.
Main.java · Two parameters may identify the same array
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Shared result: [21]
Separate results: [11] and [30]
Why is the shared result 21 rather than 20?
The first parameter changes the shared element from 10 to 11. The second parameter then reads that same updated element and adds ten. With two independent arrays, the operations reach different elements instead. The method body did not change; the aliases did.
final and null affect what you can do, not how values are passed
The final modifier prevents reassigning the local scores parameter to another reference. It does not make the referenced array elements read-only, so scores[0] = 80 remains legal and visible to the caller. final is not an immutability guarantee for the entire object graph.
Passing null to an array or String parameter copies a null reference. It does not create an empty object. A helper that indexes that array or calls a String method without a suitable guard can fail. Whether null is rejected, reported, or given another meaning is part of the helper's input contract.
A parameter guard that assigns a replacement locally cannot initialize the caller's null variable. Return a replacement and let the caller save it if that is the intended API. In this lesson's lab, null is rejected before transformation, while an actual empty array is valid and receives an independent empty result.
Main.java · final protects the parameter binding, not the elements
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Caller first score: 80
Try a useful final-parameter compilation error
Inside updateFirst, replace the element assignment with scores = new int[] {80};. Reassigning the final parameter is rejected. Restore scores[0] = 80: that changes an element instead of rebinding the parameter and is legal under this declaration.
Choose in-place mutation or a returned copy explicitly
An in-place method edits the supplied object's contents. Its contract should say which elements may change and what its return value measures, if it has one. The caller and any aliases can observe those changes. This is useful when deliberate shared updates are the point, but surprising when a caller expects a preview.
A copy-returning method allocates independent storage, transforms that storage, and returns it while leaving its input unchanged. For a one-dimensional int array, copying the element values is enough for independence. Returning the same input reference after editing it would violate that contract even if the resulting numbers looked right.
Test both values and identity. A copy with bonus zero may have identical contents but must still be a different array under this lab's contract. Test empty arrays, boundary values, repeated calls, and invalid input too. Keeping printing in main lets tests inspect the helpers' results and mutation behavior without unrelated output.
Repair exercise: return and adopt a replacement array
The caller expects a replacement array, but this helper only reassigns its own parameter. Repair it by returning the new array and saving that result in main. Keep the original array available through an alias to prove that it was not mutated.
Main.java · Find the local-only replacement
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Reveal the explained repair
The original void helper changes only its local parameter, so both caller variables still identify the original [40, 70]. The repair returns a fresh array and explicitly assigns its reference to scores in main. original keeps its old reference, and its values remain unchanged. The unused input parameter is removed because creating this fixed replacement does not depend on any old scores.
Main.java · Repaired replacement result
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Caller scores: [80, 90]
Original scores: [40, 70]
Independent lab: preview a change, then apply one deliberately
Build a score-adjustment preview and a deliberate in-place update. Add a bonus but cap every result at 100. The preview must leave the original unchanged, while the in-place operation must report how many elements actually changed. Edit the source data; no standard input or saved learner data is involved.
- Implement static boolean canAdjust(int[] scores, int bonus). Reject null, any score outside 0..100, or a bonus outside 0..100. Accept an empty array. This validator must not mutate input or print.
- Implement static int raiseInPlace(int[] scores, int bonus). Its precondition is canAdjust(...) == true. Mutate each element to Math.min(100, oldValue + bonus) and return the number of elements whose values changed.
- Implement static int[] raisedCopy(int[] scores, int bonus) with the same precondition. Copy the one-dimensional int array, adjust the copy, and return it without changing the original. Even bonus 0 and empty input must return distinct storage.
- In main validate before any transformation. On failure print Scores must be present, each 0..100, with a bonus of 0..100. and return without a normal report.
- For valid data print Original before:, Adjusted copy:, and Original after copy:. Then run the in-place helper on the original and print Changed in place: and Original after in-place:.
- Finally print Copy is independent: with the result of comparing the array references for inequality. Keep all printing in main, and verify independence by editing one array after creating the other.
The starter supplies scores of 40, 98, and 100 with a bonus of 5. No standard input is needed. Expected output:
Original before: [40, 98, 100]
Adjusted copy: [45, 100, 100]
Original after copy: [40, 98, 100]
Changed in place: 2
Original after in-place: [45, 100, 100]
Copy is independent: true
Main.java · Build your adjustment contracts
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Hints before the solution
Validate the entire input without mutating it. In raiseInPlace, calculate the capped result, compare it with the existing value, and increment changed only when they differ. The validated maximum addition is 100 + 100, so this contract avoids int overflow.
In raisedCopy, use Arrays.copyOf to create a different one-dimensional int array, call the in-place helper on that copy, and return it. Do not pass the original to the mutating helper while constructing a preview. Keep printing and the decision to apply a real update in main.
Reveal the explained lab solution
Main.java · Preview and in-place solution
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Validation examines the complete input before main calls either transformation, so a rejected later score does not cause a partial update in this workflow. raisedCopy copies primitive values into a new array and passes that new reference to raiseInPlace. The original stays unchanged until main deliberately calls raiseInPlace on it. The third score is already 100, so it contributes no change to the count. The maximum sum inside the validated contract is 200, safely within int range. The transformation helpers rely on their stated preconditions; other callers must validate too. Distinct references and independent primitive storage are both important, even when the final arrays contain equal numbers.
Test changes and boundaries
Edit the scores and bonus in source. Reset restores the original program. Compare contents as well as reference identity: equal final values do not prove that the preview shares storage, and a zero-change result does not excuse returning the original reference.
- Run the supplied data. The preview changes two values without altering the original; the later in-place update returns 2. Both final arrays contain the same numbers but have different identities.
- Try bonus 0. Values must remain unchanged, the in-place count must be 0, and the returned copy must still be a different array. Repeat the copy call and verify a fresh array is returned each time.
- Try {}. Expect empty before/after arrays, a changed count of 0, and independent copy identity. Try null and expect only the validation message.
- Try {0, 99, 100} with bonus 100. Every adjusted value is 100, but only the first two change. Reject -1 or 101 as scores and reject -1 or 101 as bonuses before mutation.
- After creating a preview, assign 0 to its first element in a nonempty test. The original must not change. Reassigning a helper parameter would not provide this independence; creating a copy does.
Common mistakes and how to diagnose them
The primitive caller variable did not change
The parameter received a copied numeric value. Changing it is local. Return a result and explicitly assign it in the caller when a new caller value is intended.
The caller sees an unexpected array edit
The copied reference still reaches shared storage. An element assignment can be visible through every alias. Use a documented in-place contract or create a copy before performing a preview.
Replacing the parameter did not replace the caller's array
The helper changed only its local reference variable. Return the replacement reference and let the caller save it. Setting the local parameter to null likewise does not clear the caller variable.
Two parameters unexpectedly apply both changes to one element
The caller may have supplied the same reference twice. Trace object identities, not just parameter names. Decide whether aliasing is permitted and include a same-array test for mutating methods.
final did not stop element mutation
final prevents rebinding the parameter, not modifying the array object. A non-mutating method needs a suitable implementation and contract; final alone is not a deep read-only guarantee.
Arguments compile but produce the wrong result
Same-typed arguments may have been reversed. Parameters are positional, so meaningful caller variable names do not automatically match them to the declaration. Check both type compatibility and domain meaning.
Frequently asked questions
Is Java pass-by-reference for objects?
No. Java always passes by value. With reference types, the copied value is a reference, allowing both caller and helper to reach the same object. Reassigning the parameter still cannot reassign the caller's variable.
Why can an array change if its reference was copied?
Copying a reference does not clone the object it identifies. Both references can reach the same mutable array, so an element update is visible through either reference.
Can a helper swap two caller reference variables by swapping its parameters?
No. It can swap only its own local reference values that way. The caller must adopt returned results or use an explicitly designed mutable holder; a local parameter swap does not rebind the caller variables.
Does a returned array have to be a new array?
No. A method can return an existing reference or a new one. Its contract must say which. This lab's raisedCopy deliberately promises independent primitive storage, so returning the input itself would be incorrect.
Must callers use the same parameter names?
No. The arguments may be literals, variables, or compatible expressions with different names. Their positions and compatible types determine the mapping, not matching spellings.
Ask which variable changes and which object is shared
Arguments supply values by position. Every parameter receives a copied value, including reference values. Element mutation and local parameter reassignment are different operations. Returning a replacement lets the caller adopt it explicitly; copying mutable data provides independence when that is part of the contract.
Before moving on, explain why a local array replacement leaves the caller alone, why two parameters may edit one element, and why final is not a read-only-array promise. Next: method overloading, where compatible argument types help choose among methods sharing a name.
Can you predict what the caller will see?
Answer all six questions and read the explanations. Results are temporary on this page and do not save course progress.
References and related learning
The scenarios, explanations, examples, and exercises are original SovranCode material. Official references support the Java 21 language rules.
- Java 21 specification: formal parameters
- Java 21 specification: method invocation and parameter variables
Review method calls and results · Review aliases and copy depth · Review immutable strings · Java course roadmap.
Last updated: