Methods and Parameters

This section documents the current Java release line, with Java 25 LTS as the reference point (no specific patch version is pinned), as published at the Java developer portal, the Java Tutorials, and the Java SE API specification — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

A method is a named block of code that takes zero or more parameters and either returns a value or is declared void. This page covers how methods are declared and called, how Java passes arguments, how the compiler chooses between overloads, and the small utility APIs every method-heavy class leans on.

Declaring and Calling Methods

A method declaration has a return type, a name, a parenthesised parameter list, and a body. The Defining Methods tutorial page describes each part; Returning a Value from a Method covers return and void.

class Geometry {

    // instance method: returns a double
    double circleArea(double radius) {
        return Math.PI * radius * radius;
    }

    // void method: performs an action, returns nothing
    void printBanner(String text) {
        System.out.println("=== " + text + " ===");
        return;                 // optional in a void method; exits early
    }

    // multiple parameters, each with its own type
    int clamp(int value, int min, int max) {
        if (value < min) return min;
        if (value > max) return max;
        return value;
    }
}

A void method may use a bare return; to exit early but cannot return a value; a non-void method must return a value of the declared type (or a subtype) on every code path or the code will not compile.

static vs. instance methods

An instance method runs against a specific object and can read that object’s fields through this. A static method belongs to the class itself, has no this, and is called through the class name. See Understanding Class Members.

class Counter {
    private int count;                       // instance field

    void increment() { count++; }            // instance method: needs an object
    int value() { return count; }

    static int sum(int... values) {          // static: no object required
        int total = 0;
        for (int v : values) total += v;
        return total;
    }
}

var c = new Counter();
c.increment();
c.increment();
System.out.println(c.value());   // 2  -- called on an instance
System.out.println(Counter.sum(1, 2, 3));   // 6  -- called on the class

Use static for pure functions that do not depend on object state (factory methods, math helpers, main); use instance methods for behaviour that acts on a particular object’s data.

Java Is Pass-by-Value

Java always passes arguments by value: the method receives a copy. For a primitive the copy is the value itself; for a reference type the copy is the reference (the pointer), not the object. Reassigning a parameter never affects the caller; following a copied reference to mutate the shared object does. This is spelled out in Passing Information to a Method or a Constructor.

import java.util.Arrays;

class PassByValue {

    static void addOne(int n) {
        n = n + 1;               // changes only the local copy
    }

    static void reassign(int[] a) {
        a = new int[] {9, 9, 9}; // rebinds the local copy of the reference
    }

    static void mutate(int[] a) {
        a[0] = 99;               // follows the reference: edits the shared array
    }

    public static void main(String[] args) {
        int x = 10;
        addOne(x);
        System.out.println(x);                     // 10  -- unchanged

        int[] nums = {1, 2, 3};
        reassign(nums);
        System.out.println(Arrays.toString(nums));  // [1, 2, 3]  -- unchanged
        mutate(nums);
        System.out.println(Arrays.toString(nums));  // [99, 2, 3]  -- changed
    }
}

The rule to remember: a method can change what a passed object contains, but it can never change which object (or value) the caller’s variable refers to.

Overloading and Overload Resolution

Two or more methods in the same class may share a name as long as their parameter lists differ in number or type — this is overloading. The return type alone is not enough to distinguish overloads. The tutorial covers the idea under Defining Methods ("Overloading Methods").

static String describe(int n)    { return "int: " + n; }
static String describe(long n)   { return "long: " + n; }
static String describe(double n) { return "double: " + n; }
static String describe(Object o) { return "Object: " + o; }
static String describe(int... n) { return "varargs, count=" + n.length; }

describe(1);        // "int: 1"
describe(1L);       // "long: 1"
describe(1.0);      // "double: 1.0"
describe("hi");     // "Object: hi"
describe(1, 2, 3);  // "varargs, count=3"

The compiler picks one overload at compile time in three phases, defined in JLS 15.12.2:

  1. without boxing/unboxing and without varargs;

  2. allowing boxing/unboxing but still no varargs;

  3. finally allowing varargs.

Within a phase the most specific applicable method wins (describe(int) is more specific than describe(long), which is more specific than describe(Object)). Because varargs is only tried last, describe(1) binds to describe(int), never to describe(int…​). An ambiguous call — where no candidate is strictly more specific — is a compile error.

Varargs methods

A trailing parameter written as Type…​ name accepts any number of arguments (including zero); inside the method it is an array. See Arbitrary Number of Arguments.

static int max(int first, int... rest) {   // at least one argument required
    int result = first;
    for (int v : rest) {
        if (v > result) result = v;
    }
    return result;
}

max(3);              // 3   -- rest is a zero-length array
max(3, 7, 1, 9, 4);  // 9
max(3, new int[] {7, 1});  // an array may be passed directly

Only one varargs parameter is allowed and it must come last. Prefer a required leading parameter (as above) when "zero arguments" would be meaningless, so the mistake is caught at compile time.

Recursion and the Call Stack

A recursive method calls itself, reducing the problem toward a base case that returns without recursing. Each call gets its own stack frame holding its parameters and locals.

static long factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);          // base case: n <= 1
}

static long gcd(long a, long b) {                       // Euclid's algorithm
    return b == 0 ? a : gcd(b, a % b);
}

static void hanoi(int n, char from, char via, char to) {
    if (n == 0) return;                                 // base case
    hanoi(n - 1, from, to, via);
    System.out.println("move disk " + n + ": " + from + " -> " + to);
    hanoi(n - 1, via, from, to);
}

factorial(5);              // 120
gcd(48, 18);               // 6
hanoi(3, 'A', 'B', 'C');   // 7 moves printed

If the base case is never reached, the stack keeps growing until the JVM throws StackOverflowError:

static int noBaseCase(int n) {
    return noBaseCase(n + 1) + 1;   // recurses forever -> StackOverflowError
}

Java does not perform tail-call optimisation, so deep recursion (tens of thousands of frames) can exhaust the stack even when logically correct. Convert such algorithms to an explicit loop or an explicit java.util.Deque stack when depth is unbounded.

Utility APIs: Math and Objects

Two java.lang/java.util classes turn up in almost every method body.

java.lang.Math holds static numeric helpers and the constants Math.PI and Math.E:

Math.max(3, 7);            // 7
Math.min(3, 7);            // 3
Math.abs(-4);              // 4
Math.pow(2, 10);           // 1024.0
Math.sqrt(2);              // 1.4142135623730951
Math.floorMod(-1, 3);      // 2   -- unlike -1 % 3, which is -1
Math.floorDiv(-7, 2);      // -4
Math.multiplyExact(1_000_000, 1_000_000);  // throws ArithmeticException on overflow
Math.round(2.5);           // 3

The *Exact methods (addExact, subtractExact, multiplyExact, incrementExact, toIntExact) throw ArithmeticException instead of silently wrapping around — use them for value-sensitive integer arithmetic. Deeper numeric coverage is on Numbers and Math.

java.util.Objects holds null-safe helpers, most useful at method entry and in equals/hashCode:

import java.util.Objects;

class Account {
    private final String id;
    private final String owner;

    Account(String id, String owner) {
        // fail fast on invalid input, with a clear message and NPE
        this.id = Objects.requireNonNull(id, "id");
        this.owner = Objects.requireNonNullElse(owner, "unknown");
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Account other)) return false;
        return Objects.equals(id, other.id)          // null-safe: no NPE if a side is null
            && Objects.equals(owner, other.owner);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, owner);              // combines fields into one hash
    }
}

Key members: requireNonNull (throws NullPointerException early, with a message), requireNonNullElse / requireNonNullElseGet (supply a fallback), equals(a, b) (treats two null`s as equal, never throws), `hash(…​) (a varargs hash for several fields), and toString(obj, nullDefault). The equals/hashCode contract these support is covered on Inheritance and Polymorphism.

See Also