Every executable statement in Java is ultimately executed within a method.
Method Declaration
A method declaration consists of an optional access modifier, optional non-access modifiers, return type, method name, parameter list, optional throws clause, and method body.public int add(int a, int b) {
return a + b;
}
A method signature consists only of the method name and parameter types. The return type is not part of the method signature.
Method Components
A method may contain the following components.- Access Modifier
- Non-Access Modifiers
- Return Type
- Method Name
- Parameter List
- Throws Clause
- Method Body
public static final int calculate(int value) throws Exception {
return value * 2;
}
Access Modifiers
Methods can use one of the following access modifiers.- public allows access from anywhere.
- protected allows access within the package and subclasses.
- Package-private (no modifier) allows access only within the package.
- private allows access only within the declaring class.
public void publicMethod() {}
protected void protectedMethod() {}
void packagePrivateMethod() {}
private void privateMethod() {}
Non-Access Modifiers
Common non-access modifiers include:- static
- final
- abstract
- synchronized
- native
- strictfp
These modifiers change method behavior and are discussed in detail in their respective topics.
Return Type
A method may return a value or return nothing.public int square(int number) {
return number * number;
}
public void printMessage() {
System.out.println("Hello");
}
Every execution path of a non-void method must return a compatible value.
Method Naming
Method names should clearly describe behavior and typically begin with a verb. Examples include:calculateSalary()
findEmployee()
saveOrder()
sendEmail()
isValid()
Boolean-returning methods commonly begin with is, has, or can.
Parameters and Arguments
- Parameters are variables declared in the method definition.- Arguments are values supplied during method invocation.
public void greet(String name) {
System.out.println(name);
}
greet("John");
Here, name is the parameter and "John" is the argument.
Java is Always Pass-by-Value
Java always uses pass-by-value. For primitive types, the actual value is copied.void increment(int value) {
value++;
}
int number = 10;
increment(number);
System.out.println(number);
Output:
10
The original variable remains unchanged.
For objects, the reference value is copied, not the object.
class Employee {
String name;
}
void change(Employee employee) {
employee.name = "Alice";
}
Employee employee = new Employee();
employee.name = "John";
change(employee);
System.out.println(employee.name);
Output:
Alice
Both variables refer to the same object because the object reference was copied. Reassigning the copied reference does not affect the original variable.
void reset(Employee employee) {
employee = new Employee();
}
Employee employee = new Employee();
reset(employee);
The caller still references the original object.
Local Variables
Variables declared inside a method exist only during method execution.void display() {
int number = 100;
System.out.println(number);
}
Local variables must be initialized before use.
Variable Scope
The scope of a variable is limited to the block in which it is declared.if (true) {
int value = 100;
}
// value is not accessible here
Keeping variable scope as small as possible improves readability and reduces bugs.
Method Overloading
Methods may have the same name provided their parameter lists differ.int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
Changing only the return type does not create a valid overload.
Varargs
Varargs allow a method to accept a variable number of arguments.public int sum(int... numbers) {
int total = 0;
for (int number : numbers) {
total += number;
}
return total;
}
Method invocation:
sum();
sum(10);
sum(10, 20, 30);
Internally, varargs are implemented as arrays. A method can declare only one varargs parameter, and it must be the last parameter.
Recursive Methods
A method can invoke itself.int factorial(int number) {
if (number == 1) {
return 1;
}
return number * factorial(number - 1);
}
Every recursive method must define a termination condition to prevent infinite recursion. Deep recursion may result in StackOverflowError.
Static Methods
Static methods belong to the class rather than an object.class MathUtil {
static int square(int number) {
return number * number;
}
}
MathUtil.square(10);
Static methods cannot directly access instance variables or invoke instance methods.
Instance Methods
Instance methods operate on object state.class Employee {
String name;
void display() {
System.out.println(name);
}
}
Instance methods require an object for invocation.
Method Hiding
Static methods are hidden, not overridden.class Parent {
static void display() {
System.out.println("Parent");
}
}
class Child extends Parent {
static void display() {
System.out.println("Child");
}
}
The method executed depends on the reference type rather than the runtime object.
Method Overriding
A subclass may provide a different implementation for an inherited method.class Animal {
void sound() {
System.out.println("Animal");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Bark");
}
}
Runtime method selection is based on the actual object type.
Covariant Return Types
An overridden method may return a subtype of the original return type.class Animal {
}
class Dog extends Animal {
}
class AnimalFactory {
Animal create() {
return new Animal();
}
}
class DogFactory extends AnimalFactory {
@Override
Dog create() {
return new Dog();
}
}
Covariant return types improve type safety.
Method References
Java supports method references as a concise alternative to simple lambda expressions.List<String> names = List.of("John", "Alice");
names.forEach(System.out::println);
Method references improve readability when the lambda only invokes an existing method.
Generic Methods
Methods may declare their own type parameters.public <T> void print(T value) {
System.out.println(value);
}
Generic methods provide compile-time type safety while avoiding unnecessary casting.
throws Clause
A method can declare checked exceptions using the throws clause.public void readFile() throws IOException {
}
Exception handling is covered separately.
Native Methods
Native methods are implemented in languages such as C or C++ and invoked through the Java Native Interface (JNI).public native void loadLibrary();
Native methods do not contain Java implementations.
synchronized Methods
The synchronized modifier ensures only one thread executes the method on a particular object at a time.public synchronized void increment() {
count++;
}
Synchronization is discussed in detail in the Multithreading and Concurrency article.
Method Invocation Stack
Each method invocation creates a new stack frame in the thread's call stack. The stack frame stores local variables, parameters, return information, and intermediate values.When the method completes, its stack frame is automatically removed. Recursive method calls create multiple stack frames.
The JVM stack architecture is discussed in detail in the JVM Internals article.
Tail Recursion
Although some programming languages optimize tail-recursive methods, the Java compiler and JVM do not perform tail-call optimization. Large recursive calls may therefore consume significant stack memory. Iterative solutions are generally preferred for deep recursion.
Method Design Guidelines
Methods should perform one well-defined task, have descriptive names, minimize side effects, avoid excessive parameter lists, return meaningful values, and expose only the behavior required by callers. Small, cohesive methods improve readability, maintainability, testing, and reuse.