Java Method Parameters
A method parameter is a variable declared inside the parentheses of a method’s signature, and it lets you feed data into a method so the method can act on different values each time it is called. Parameters are the reason methods are reusable: instead of writing one block of code per input, you write the logic once and pass in whatever data you need at call time. Understanding exactly how Java hands data to a parameter, and what that method is and is not allowed to do with it, is essential for avoiding a whole category of subtle bugs.
Overview: How Method Parameters Work
A parameter is the variable listed in a method’s declaration (for example, int width in calculateArea(int width, int height)). An argument is the actual value supplied when the method is called (for example, the 5 in calculateArea(5, 3)). People often use the two words interchangeably, but the distinction matters when you’re reading compiler error messages: “parameter” refers to the method’s declaration, “argument” refers to the caller’s data.
Every method parameter becomes a brand-new local variable that lives only for the duration of that method call. When you call a method, the Java Virtual Machine creates a fresh stack frame for that call, and each parameter gets its own storage slot inside that frame. The values from the caller are copied into those slots. This is the single most important fact about Java parameters: Java is always pass-by-value. There is no pass-by-reference in Java, ever — not even for objects.
This confuses many learners because objects seem to be passed “by reference”. Here is the precise rule: when the parameter type is a primitive (int, double, boolean, etc.), Java copies the primitive value itself. When the parameter type is an object type (a class, array, or interface type), Java copies the reference — essentially a pointer to where the object lives on the heap — not the object. Because the copied reference still points at the same heap object as the caller’s reference, a method can reach through that reference and change the object’s fields, and the caller will see those changes. But if the method reassigns the parameter itself to point at a different object, that only changes the local copy of the reference; the caller’s variable still points at the original object. Both behaviors are 100% consistent with “pass a copy of the value” — it’s just that for objects, the value being copied happens to be an address.
A few more structural rules govern parameters: the number and order of arguments in a call must match the number and order of parameters in the declaration (unless the method is overloaded or uses varargs). Argument types must match the parameter types exactly, or be convertible to them through an automatic widening conversion (for example, passing an int where a double is expected is allowed because int widens to double automatically). Parameters are scoped to the method body — they are not visible outside it, and they can be reassigned freely inside the method without any special syntax, unless declared final.
Syntax
returnType methodName(paramType1 paramName1, paramType2 paramName2, ...) {
// method body can use paramName1, paramName2, ...
}
| Part | Description |
|---|---|
returnType |
The data type of the value the method sends back, or void if it returns nothing. |
methodName |
The identifier used to call the method. |
paramType |
The declared type of each parameter (primitive or reference type). Every parameter must have its own type; Java does not let you share one type across a comma list like int a, b. |
paramName |
The local variable name used to refer to that value inside the method body. |
| Parameter list | The full comma-separated group inside the parentheses; it can be empty, or contain any number of parameters, each separated by a comma. |
Examples
Example 1: A method with two parameters
public class Main {
public static void main(String[] args) {
int area = calculateArea(5, 3);
System.out.println("Area: " + area);
}
static int calculateArea(int width, int height) {
return width * height;
}
}
Area: 15
Here width and height are parameters of type int. When main calls calculateArea(5, 3), the JVM copies the value 5 into width and 3 into height in a new stack frame for that call. The method multiplies its local copies and returns the result; main‘s own variables are untouched by anything happening inside calculateArea.
Example 2: Multiple parameters of different types
public class Main {
public static void main(String[] args) {
printProfile("Ravi", 28, 5.9);
}
static void printProfile(String name, int age, double heightFt) {
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Height: " + heightFt + " ft");
}
}
Name: Ravi
Age: 28
Height: 5.9 ft
This method takes three parameters of three different types: a String, an int, and a double. Java matches arguments to parameters strictly by position, not by name — the first argument always fills the first parameter slot, the second fills the second, and so on. Mixing up the order (even if the types happened to line up) would silently produce wrong results, which is exactly the kind of mistake covered later in this lesson.
Example 3: Pass-by-value with primitives vs. objects
class Counter {
int value;
Counter(int value) {
this.value = value;
}
}
public class Main {
public static void main(String[] args) {
int number = 10;
modifyPrimitive(number);
System.out.println("After modifyPrimitive: " + number);
Counter counter = new Counter(10);
modifyObjectField(counter);
System.out.println("After modifyObjectField: " + counter.value);
reassignReference(counter);
System.out.println("After reassignReference: " + counter.value);
}
static void modifyPrimitive(int n) {
n = 999;
}
static void modifyObjectField(Counter c) {
c.value = 999;
}
static void reassignReference(Counter c) {
c = new Counter(-1);
}
}
After modifyPrimitive: 10
After modifyObjectField: 999
After reassignReference: 999
This example demonstrates the whole story in one program. modifyPrimitive receives a copy of the int value 10; changing that copy to 999 has zero effect on number in main. modifyObjectField receives a copy of the reference to the Counter object, but that copy still points at the exact same object on the heap as counter does, so writing to c.value changes the one shared object — and main sees 999. Finally, reassignReference points its local copy c at a brand-new Counter(-1) object; this only redirects the local copy of the reference, so counter back in main still points at the original object, which still holds 999.
Under the Hood: How Java Passes Arguments
Step by step, here is what actually happens when you call a method with arguments:
- The caller evaluates each argument expression down to a single value (a primitive value, or an object reference).
- The JVM pushes a new stack frame for the method call, with one storage slot reserved per parameter.
- Each argument’s value is copied bit-for-bit into the corresponding parameter slot in that new frame. For primitives, this copies the actual number/boolean; for objects and arrays, this copies the reference (the address), not the object’s contents.
- The method body runs using only its own local copies. Any reassignment of a parameter (
n = 999;orc = new Counter(-1);) only overwrites that local slot. - If a parameter is a reference and the method dereferences it to modify fields (
c.value = 999;), it is reaching into the one shared object on the heap, so that change is visible to everyone holding a reference to that same object — including the caller. - When the method returns, its entire stack frame (including all parameter slots) is discarded. Only the return value (if any) and any heap mutations survive.
This model is why primitives are often described as passed “by value” and objects as passed “by reference” in casual conversation — but strictly speaking, Java only ever does one thing: it copies the value that sits in the variable, whether that value is a number or an address.
Common Mistakes
Mistake 1: Expecting a reassigned primitive parameter to change the caller’s variable
public class Main {
public static void main(String[] args) {
double price = 100.0;
applyMarkup(price);
System.out.println("Price after markup: " + price);
}
static void applyMarkup(double price) {
price = price * 1.5;
}
}
Price after markup: 100.0
This compiles and runs fine, but it does not do what the author intended. applyMarkup only reassigns its own local copy of price; the price variable in main never changes because primitives are copied by value. The fix is to have the method return the new value and have the caller store it:
public class Main {
public static void main(String[] args) {
double price = 100.0;
price = applyMarkup(price);
System.out.println("Price after markup: " + price);
}
static double applyMarkup(double price) {
return price * 1.5;
}
}
Price after markup: 150.0
Mistake 2: Swapping the order of same-type arguments
public class Main {
public static void main(String[] args) {
double price = pricePerItem(4, 100);
System.out.println("Price per item: $" + price);
}
static double pricePerItem(double totalPrice, int quantity) {
return totalPrice / quantity;
}
}
Price per item: $0.04
The intent was a $100 total split across 4 items ($25 each), but the caller accidentally passed 4 as the total price and 100 as the quantity. The compiler cannot catch this because both values are numeric and both widen/convert successfully — the bug is purely logical. Since Java matches arguments to parameters strictly by position, always double-check argument order against the parameter list, especially when several parameters share a type:
public class Main {
public static void main(String[] args) {
double price = pricePerItem(100, 4);
System.out.println("Price per item: $" + price);
}
static double pricePerItem(double totalPrice, int quantity) {
return totalPrice / quantity;
}
}
Price per item: $25.0
Mistake 3: Calling a method with the wrong number of arguments
public class Main {
public static void main(String[] args) {
int sum = add(5);
System.out.println(sum);
}
static int add(int a, int b) {
return a + b;
}
}
This fails to compile with an error similar to “method add in class Main cannot be applied to given types; required: int,int; found: int; reason: actual and formal argument lists differ in length”. Every parameter in the declaration needs a matching argument in the call, in the same order:
public class Main {
public static void main(String[] args) {
int sum = add(5, 10);
System.out.println(sum);
}
static int add(int a, int b) {
return a + b;
}
}
15
Best Practices
- Keep parameter lists short (roughly 3-4 or fewer); if a method needs many related values, bundle them into a small class or record instead.
- Give parameters descriptive names that include units or intent, such as
heightFtorquantity, so misordering mistakes are easier to spot at the call site. - Never rely on reassigning a primitive parameter to communicate a result back to the caller — return the value instead.
- Be deliberate about mutating object parameters; if a method changes the fields of an object passed to it, document that clearly, since callers may not expect their object to be altered.
- Declare a parameter
finalwhen you want the compiler to guarantee it is never reassigned inside the method, which also makes the method easier to reason about. - Validate parameters at the top of the method (for example, checking that a quantity is positive) and throw an exception early rather than letting bad data propagate silently.
- Avoid multiple parameters of the same type sitting next to each other when possible; if you must, add extra care (or overloads/builder patterns) to reduce the chance of an order mix-up.
- Prefer passing immutable objects (like
Stringor wrapper types) when a method should not be able to affect the caller’s data at all.
Practice Exercises
- Write a method
attemptSwap(int a, int b)that tries to swap the values of twointparameters inside the method body. Call it frommainwith two local variables and print them before and after the call. Explain why the values inmaindo not actually swap. - Create a simple
Employeeclass with adouble salaryfield. Write a methodgiveRaise(Employee emp, double percent)that increases the employee’s salary by the given percentage by modifying the field directly. Verify frommainthat the change is visible after the method returns. - Write a method
average(String studentName, int score1, int score2)that validates both scores are between 0 and 100 (inclusive) and returns their average as adouble; if either score is out of range, have it print an error message and return-1instead.
Summary
- A parameter is a variable in a method’s declaration; an argument is the actual value supplied at the call site.
- Java is always pass-by-value: primitives copy their value, and objects/arrays copy their reference (address), never the object itself.
- Because a copied reference still points at the same heap object, methods can mutate an object’s fields and have the caller see the change, but reassigning the parameter to a new object never affects the caller’s variable.
- Arguments are matched to parameters strictly by position and must match (or widen to) the declared parameter types.
- Each method call gets its own stack frame with fresh copies of its parameters, which are discarded when the method returns.
- Common bugs include expecting a reassigned primitive to update the caller, swapping same-typed arguments, and passing the wrong number of arguments.
- Favor short, well-named parameter lists, validate inputs early, and return new values instead of relying on side effects when possible.
