Intermediate

Functions in Java

Learn how to define and call methods in Java - parameters, return values, overloading, recursion, varargs, and higher-order functions with Docker-ready examples

In Java, the unit of reusable behavior is the method. Because Java is an object-oriented, class-based language, there are no free-standing functions the way there are in C or Python — every method lives inside a class. The methods you have already used, like main, are just the most visible example. Once you understand how to define your own methods, you can break a program into small, named, reusable pieces.

This tutorial focuses on static methods, which belong to the class itself rather than to any particular object. Static methods are the closest Java equivalent to the plain “functions” you find in other languages, which makes them the natural starting point. You call them directly by name within the same class, without needing to create an instance first — exactly how main is able to call other helpers.

You will learn how to define methods with parameters and return values, how Java uses method overloading instead of default parameters, how methods call themselves through recursion, how to accept a variable number of arguments with varargs, and how Java’s functional interfaces let you pass behavior around as higher-order functions.

Defining and Calling Methods

A method declaration names the method, lists its parameters with their types, and declares the type of value it returns. Java is statically and strongly typed, so every parameter and the return value must have an explicit type, and the compiler checks every call against that signature. A method that returns nothing uses the void keyword.

The example below defines several methods and calls them from main. Notice that each method sits beside main inside the same class, and that the return type comes before the method name.

Create a file named Functions.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import java.util.function.Function;

public class Functions {

    // A method with two parameters that returns a value
    static int add(int a, int b) {
        return a + b;
    }

    // A void method performs an action but returns no value
    static void greet(String name) {
        System.out.println("Hello, " + name + "!");
    }

    // Method overloading: same name, different parameter lists.
    // This is Java's idiomatic alternative to default parameters.
    static int multiply(int a, int b) {
        return a * b;
    }

    static int multiply(int a, int b, int c) {
        return a * b * c;
    }

    // Recursion: a method that calls itself.
    // 'long' is used because factorials grow quickly.
    static long factorial(int n) {
        if (n <= 1) {
            return 1;
        }
        return n * factorial(n - 1);
    }

    // Varargs: accept any number of int arguments as an array
    static int sum(int... numbers) {
        int total = 0;
        for (int number : numbers) {
            total += number;
        }
        return total;
    }

    // Higher-order method: takes a function as a parameter
    // and applies it twice to the given value.
    static int applyTwice(Function<Integer, Integer> operation, int value) {
        return operation.apply(operation.apply(value));
    }

    public static void main(String[] args) {
        // Use a method's return value
        int result = add(7, 5);
        System.out.println("add(7, 5) = " + result);

        // Call a void method for its side effect
        greet("Ada");

        // The compiler picks the overload by argument count
        System.out.println("multiply(3, 4) = " + multiply(3, 4));
        System.out.println("multiply(3, 4, 5) = " + multiply(3, 4, 5));

        // Recursion
        System.out.println("factorial(5) = " + factorial(5));

        // Varargs lets you pass as many arguments as you like
        System.out.println("sum(1, 2, 3, 4) = " + sum(1, 2, 3, 4));

        // Pass behavior as data using a lambda expression
        int twice = applyTwice(x -> x + 10, 5);
        System.out.println("applyTwice(x -> x + 10, 5) = " + twice);
    }
}

Parameters and Return Values

The add method declares two int parameters and an int return type. Java passes arguments by value: the method receives a copy of each argument, so reassigning a parameter inside the method never affects the caller’s variable. (For object references the reference is copied, so the method can still mutate the object it points to — but the caller’s variable still points to the same object afterward.)

The return statement both hands a value back to the caller and ends the method immediately. A void method like greet has no return type to satisfy, so it can simply fall off the end, or use a bare return; to exit early.

Overloading Instead of Default Parameters

Java does not have default parameter values the way Python or C# do. Instead, it relies on method overloading: you define several methods with the same name but different parameter lists, and the compiler chooses which one to call based on the arguments you supply. The two multiply methods above show this — one takes two arguments, the other takes three. This is resolved entirely at compile time.

Recursion

A recursive method calls itself with a smaller input until it reaches a base case that stops the recursion. In factorial, the base case is n <= 1, which returns 1 without recursing further. Every other call multiplies n by the factorial of n - 1. Each call adds a frame to the call stack, so very deep recursion can exhaust the stack — but for a problem like factorial it is clear and concise.

Varargs and Higher-Order Methods

The sum method uses varargs (int... numbers), which lets the caller pass any number of arguments; inside the method they arrive as an array you can loop over. The applyTwice method is a higher-order method: it accepts a Function<Integer, Integer> and a value, then applies that function twice. At the call site we supply the function as a lambda expression, x -> x + 10, a feature added in Java 8 that makes treating behavior as data far more natural than the older anonymous-class syntax.

Running with Docker

1
2
3
4
5
6
# Pull the official Eclipse Temurin JDK image
docker pull eclipse-temurin:21-jdk

# Compile and run the program
docker run --rm -v $(pwd):/app -w /app eclipse-temurin:21-jdk \
    sh -c "javac Functions.java && java Functions"

Expected Output

add(7, 5) = 12
Hello, Ada!
multiply(3, 4) = 12
multiply(3, 4, 5) = 60
factorial(5) = 120
sum(1, 2, 3, 4) = 10
applyTwice(x -> x + 10, 5) = 25

Key Concepts

  • Methods live inside classes — Java has no top-level functions; every method belongs to a class, and static methods belong to the class itself rather than to an instance.
  • Explicit types everywhere — Java’s static, strong typing requires a declared type for every parameter and return value, and the compiler verifies each call against the method’s signature.
  • Arguments are passed by value — methods receive copies of their arguments; for object references the reference is copied, so the object can be mutated but the caller’s variable is never reassigned.
  • Overloading replaces default parameters — define multiple methods with the same name and different parameter lists, and the compiler selects the right one at compile time.
  • Recursion needs a base case — a method that calls itself must have a stopping condition, or it will recurse until the call stack overflows.
  • Varargs handle flexible argument countsType... name collects extra arguments into an array the method can iterate over.
  • Methods can take methods — functional interfaces like Function<T, R> plus lambda expressions let you pass behavior as data, enabling higher-order programming since Java 8.
  • Scope is block-based — a variable declared inside a method (like total in sum) is local to that method and invisible to the rest of the program.

Running Today

All examples can be run using Docker:

docker pull eclipse-temurin:21-jdk
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining