Java Parameters
Parameters are the inputs a method declares. They let one method work with different data each time it is called: a calculateTax method computes tax for whatever amount it receives, rather than for one hard-coded number. Understanding exactly how Java passes values into parameters explains why some changes inside a method are visible to the caller and others are not.
Declaring Parameters
Parameters are listed in the parentheses after the method name, each with a type and a name, separated by commas:
static void printReceipt(String customer, double amount, boolean paid) {
System.out.println("Customer: " + customer);
System.out.printf("Amount: %.2f%n", amount);
System.out.println("Status: " + (paid ? "Paid" : "Due"));
}
printReceipt("Ada", 1499.50, true);
Output:
Customer: Ada
Amount: 1499.50
Status: Paid
Inside the method, each parameter behaves like a local variable that has already been assigned the corresponding argument. It exists only while the method runs.
Parameters Versus Arguments
| Term | Where it appears | Example |
|---|---|---|
| Parameter | The method declaration | String customer |
| Argument | The method call | "Ada" |
Arguments are matched to parameters by position, not by name. Java has no named arguments.
static double rectangleArea(double width, double height) {
return width * height;
}
double height = 3, width = 10;
rectangleArea(height, width); // width parameter receives 3, height receives 10
The variable names at the call site are irrelevant; only the order counts. For multiplication the mix-up is harmless, but with transfer(fromAccount, toAccount, amount) swapping the first two would move money the wrong way. Methods with several parameters of the same type deserve careful naming and, when the list grows long, a parameter object (covered below).
Type Checking and Conversion
Each argument must be assignable to its parameter's type. Widening conversions happen automatically; narrowing ones do not:
static void show(double value) { System.out.println(value); }
show(5); // int widens to double: prints 5.0
show(5L); // long widens to double
static void count(int n) { System.out.println(n); }
count(5.0); // compile error: double cannot be narrowed to int
count((int) 5.9); // explicit cast: prints 5
The compiler also checks the number of arguments. Calling a method with too few or too many is a compile error unless the method uses varargs.
Pass-by-Value
Java passes every argument by value: the parameter receives a copy of the argument's value. What that copy contains depends on whether the type is a primitive or a reference.
Primitives: the method gets a copy of the number
static void addTen(int x) {
x = x + 10;
System.out.println("Inside: " + x);
}
int score = 50;
addTen(score);
System.out.println("Outside: " + score);
Output:
Inside: 60
Outside: 50
x is a separate variable holding a copy of 50. Changing x has no effect on score. To get a changed value back, the method must return it: score = addTen(score); with addTen returning x + 10.
References: the method gets a copy of the reference
For objects and arrays, the value copied is the reference, the address of the object. Caller and method now hold two references to the same object.
static void applyBonus(int[] salaries) {
for (int i = 0; i < salaries.length; i++) {
salaries[i] += 1000;
}
}
int[] team = {30000, 42000};
applyBonus(team);
System.out.println(Arrays.toString(team)); // [31000, 43000]
The method modified the array through its copy of the reference, and the caller sees the change because there is only one array.
Reassigning a reference parameter does not affect the caller
static void replace(int[] salaries) {
salaries = new int[]{0, 0}; // the local copy now points to a new array
salaries[0] = 99; // changes the new array only
}
int[] team = {30000, 42000};
replace(team);
System.out.println(Arrays.toString(team)); // [30000, 42000]
Assigning a new object to the parameter only redirects the method's copy. The caller's team variable still points to the original array.
| Inside the method | Affects the caller? |
|---|---|
| Reassign a primitive parameter | No |
Reassign a reference parameter (p = new ...) | No |
Modify the object through a reference (p.setX(), arr[i] = ...) | Yes |
Immutable objects behave like primitives
String, Integer, LocalDate, and other immutable types have no methods that modify the object. Every "change" creates a new object, which is a reassignment:
static void shout(String s) {
s = s.toUpperCase(); // new String assigned to local s
}
String msg = "hello";
shout(msg);
System.out.println(msg); // hello
The method must return the new string for the caller to receive it.
final Parameters
Marking a parameter final prevents the method from reassigning it:
static double net(final double gross, final double taxRate) {
gross = gross * 0.9; // compile error: cannot assign a final parameter
return gross * (1 - taxRate);
}
This guarantees the parameter holds the original argument throughout the method, which helps when reading long methods. It does not make a referenced object immutable; final List<String> items can still have elements added.
Variable Arguments (Varargs)
A method can accept any number of arguments of one type by placing ... after the type of its last parameter:
static int sum(int... numbers) {
int total = 0;
for (int n : numbers) {
total += n;
}
return total;
}
System.out.println(sum()); // 0
System.out.println(sum(5)); // 5
System.out.println(sum(1, 2, 3, 4)); // 10
System.out.println(sum(new int[]{7, 8})); // 15
Inside the method, numbers is an ordinary int[]. The compiler packs the arguments into an array automatically. Rules:
- Only one varargs parameter per method, and it must be the last.
- Calling with no arguments gives an empty array, not
null. - An existing array can be passed directly.
static String join(String separator, String... parts) {
return String.join(separator, parts);
}
join(", ", "red", "green", "blue"); // "red, green, blue"
String.format, System.out.printf, List.of, and Arrays.asList all use varargs.
Many Parameters: Use a Parameter Object
Methods with long parameter lists are hard to call correctly:
createUser("Ada", "Lovelace", "ada@example.com", "+91-9000000000", 36, true, false);
Which boolean means what? Grouping related values into a record makes calls self-explanatory and type-checked:
record UserInfo(String firstName, String lastName, String email,
String phone, int age) {}
static void createUser(UserInfo info, boolean sendWelcome) { ... }
createUser(new UserInfo("Ada", "Lovelace", "ada@example.com", "+91-9000000000", 36), true);
Adding a field later changes the record, not every method signature and call site.
Validating Parameters
A method should reject arguments it cannot handle, at the start, before doing any work:
static double withdraw(double balance, double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive: " + amount);
}
if (amount > balance) {
throw new IllegalArgumentException("Insufficient balance");
}
return balance - amount;
}
For reference parameters that must not be null, Objects.requireNonNull throws a clear exception at the boundary instead of a confusing one deep inside the method:
import java.util.Objects;
static void register(String email) {
Objects.requireNonNull(email, "email must not be null");
...
}
Practical Example: Discount Engine
import java.util.Arrays;
public class Discounts {
static double finalPrice(double price, double percentOff, double maxDiscount) {
if (price < 0 || percentOff < 0 || percentOff > 100) {
throw new IllegalArgumentException("Invalid price or percentage");
}
double discount = Math.min(price * percentOff / 100, maxDiscount);
return price - discount;
}
static void applyToAll(double[] prices, double percentOff, double maxDiscount) {
for (int i = 0; i < prices.length; i++) {
prices[i] = finalPrice(prices[i], percentOff, maxDiscount);
}
}
public static void main(String[] args) {
double single = 2000;
double discounted = finalPrice(single, 20, 300);
System.out.println(single + " -> " + discounted); // 2000.0 -> 1700.0
double[] cart = {500, 1200, 4000};
applyToAll(cart, 15, 400);
System.out.println(Arrays.toString(cart)); // [425.0, 1020.0, 3600.0]
}
}
finalPrice takes primitives, so it cannot alter the caller's single; it returns the new price instead. applyToAll receives a reference to the caller's array and updates it in place, which is why cart changes after the call. Both methods validate or delegate validation before computing. The cap parameter (maxDiscount) shows why parameters make a method reusable: the same logic supports a 20% sale capped at 300 and a 15% sale capped at 400 without any changes to the method.