Parameters are the inputs a Java method receives and return values are the results it hands back. The single most important thing to understand here is that Java passes every argument by value, so a method that reassigns one of its parameters leaves the caller's variable untouched.
How do arguments reach a method?
When you call a method, the values you pass sit in the parentheses, and the method's parameter list names where each one lands. The count and the types have to line up, and the compiler checks that for you before the program runs.
public class ParameterPassing {
static void doubleIt(int value) {
value = value * 2;
System.out.println("inside method, value = " + value);
}
public static void main(String[] args) {
int score = 10;
doubleIt(score);
System.out.println("after method, score = " + score);
}
}The output is the surprise that trips up nearly everyone:
inside method, value = 20
after method, score = 10The method doubled its own copy of the number and the caller's variable never heard about it. If you want the caller to end up with the doubled value, the method has to give it back with a return statement and the caller has to assign it.
Why does Java pass arguments by value?
Java copies the value into the parameter. For an int that copy is the number itself. For an object the copy is the reference, so the method gets its own copy of the pointer and both copies point at the same object. That is why a method can change the contents of an object it was given but cannot swap the caller's reference for a different object.
| Argument type | What is copied | Reassigning the parameter |
|---|---|---|
Primitive such as int | The value | Caller sees no change |
Immutable object such as String | The reference | Caller sees no change |
Mutable object such as StringBuilder | The reference | Caller sees no change, but content changes are shared |
| Array | The reference | Caller sees no change, but element changes are shared |
The rule is the same in every row: the parameter variable is always a copy. What varies is whether the thing the copy points at can be modified in place.
When does a change inside a method become visible outside?
When the method modifies the contents of a mutable object rather than replacing the parameter. A StringBuilder is the easiest thing to show, because it exists to be changed.
public class MutableObject {
static void appendNote(StringBuilder notes, String note) {
if (notes.length() > 0) {
notes.append(" / ");
}
notes.append(note);
}
public static void main(String[] args) {
StringBuilder notes = new StringBuilder();
appendNote(notes, "read chapter 4");
appendNote(notes, "solve exercises");
System.out.println("notes = " + notes);
}
}The caller sees everything the method added:
notes = read chapter 4 / solve exercisesThe method never reassigned its parameter, so the reference stayed the same and both sides kept pointing at the same StringBuilder. Swap the parameter for a brand new StringBuilder inside the method and the caller would go back to an empty set of notes.
Should a method change its input or return a new value?
Both are legitimate, and the choice is about intent. Returning a new value leaves the input alone, which makes the method predictable and easy to reason about. It is the better default when the caller may still want the original.
public class ReturnNewValue {
static String normalise(String raw) {
return raw.trim().toLowerCase().replaceAll("\\s+", " ");
}
public static void main(String[] args) {
String input = " Order Status ";
String cleaned = normalise(input);
System.out.println("[" + cleaned + "]");
System.out.println("original still: [" + input + "]");
}
}The output shows the cleaned text and the untouched original side by side:
[order status]
original still: [ Order Status ]That is the appeal of returning a new value: the caller keeps both versions and can decide which one to keep. Strings are immutable, so this style is the only option for them. For a list you are accumulating into, mutating in place is usually what you want, and the method name should say so.
How do you chain the results of several calls?
A returned value is an ordinary value, so you can call methods on it directly. This is the style you will see in most real Java code.
public class ReturnChaining {
static String buildLabel(String product, int quantity) {
return product + " x" + quantity;
}
public static void main(String[] args) {
System.out.println(buildLabel("Notebook", 3).toUpperCase());
System.out.println(buildLabel("Pen", 12).toUpperCase().replace("X", "*"));
}
}The output is:
NOTEBOOK X3
PEN *12Each call takes the value produced by the one before it. Chaining reads well, but it has a limit: a long chain is hard to debug because you cannot see the intermediate values. If the chain grows past two or three links, assign the intermediate results to named variables so the steps are visible.
When should a method return nothing at all?
When the method exists for its effect, such as printing a receipt or updating an array in place. Then the useful thing it can give back is the state it changed, not a value.
public class VoidOrReturn {
static void applyDiscountToArray(double[] prices, double rate) {
for (int i = 0; i < prices.length; i++) {
prices[i] = prices[i] * (1 - rate);
}
}
static double total(double[] prices) {
double sum = 0;
for (double p : prices) {
sum += p;
}
return sum;
}
public static void main(String[] args) {
double[] prices = {10.00, 20.00};
applyDiscountToArray(prices, 0.10);
System.out.printf("total after discount: %.2f%n", total(prices));
}
}The output is:
total after discount: 27.00The discount method returns nothing because the array itself now holds the new prices, and a separate method computes the total from it. Splitting the two means you can apply different rates, or reuse the total calculation on an array that was never discounted.
Practical rules
- Arguments are passed by value, so a method cannot change the caller's variable by reassigning a parameter.
- To change the caller's value, return the new one and assign it at the call site.
- Changes made to the contents of a mutable object or an array are visible to the caller, because both sides share the same object.
- Return a new value for immutable inputs such as strings, and mutate in place for accumulators you own.
- Chain two or three calls at most; beyond that, use named variables so the steps stay visible.
- Choose between void and a return value based on what the caller needs, not on what is easier to type.
Try it yourself: write a method called applyDiscount(double[] prices, double rate) that returns nothing, and a second one called discountedTotal(double[] prices, double rate) that returns the discounted total without touching the original array. Call both on the same array and print the array afterwards, so you can see for yourself which one changed the caller's data. Then add a method that takes a price and returns a formatted string, and call it inside the printing of the total.