JavaFunctional Interfaces

Functional Interfaces

A functional interface is an interface that declares exactly one abstract method. That single abstract method is what a lambda expression or method reference implements. A functional interface can still declare any number of default and static methods — those do not count toward the “one abstract method” rule, because they already have a body.

The @FunctionalInterface annotation

Java lets you mark an interface with @FunctionalInterface. The annotation itself does not change how the interface behaves — it is optional, and a lambda works fine on an unannotated single-method interface. Its real value is as a safety net: if you (or a future teammate) accidentally add a second abstract method to an annotated interface, the compiler raises an error immediately instead of letting existing lambda-based code silently break.

A custom functional interface

Java
@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);

    // Allowed — default methods don't count as abstract methods
    default Calculator andThenDouble() {
        return (a, b) -> calculate(a, b) * 2;
    }
}

public class Main {
    public static void main(String[] args) {
        Calculator add = (a, b) -> a + b;
        System.out.println(add.calculate(3, 4));               // 7
        System.out.println(add.andThenDouble().calculate(3, 4)); // 14
    }
}

If a second abstract method like int calculateTwice(int a); were added to Calculator above, the @FunctionalInterface annotation would make the compiler reject the interface right away, with a clear error message — without the annotation, the mistake would only surface later, wherever a lambda tried (and failed) to implement the now-ambiguous interface.

The built-in functional interfaces

You rarely need to declare your own functional interface for common shapes — the java.util.function package ships a standard library of them, used throughout the Stream API and elsewhere.

Interface

Abstract method

Purpose

Function<T, R>

R apply(T t)

Takes a T, returns an R — a transformation

Predicate<T>

boolean test(T t)

Takes a T, returns a boolean — a yes/no test

Consumer<T>

void accept(T t)

Takes a T, returns nothing — an action performed on a value

Supplier<T>

T get()

Takes nothing, returns a T — a source of values

BiFunction<T, U, R>

R apply(T t, U u)

Takes a T and a U, returns an R — a two-argument transformation

One example of each

Java
Function<String, Integer> length = s -> s.length();
System.out.println(length.apply("hello"));            // 5

Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4));                    // true

Consumer<String> printer = s -> System.out.println("Got: " + s);
printer.accept("world");                                // Got: world

Supplier<Double> randomValue = () -> Math.random();
System.out.println(randomValue.get());                  // some random double

BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;
System.out.println(multiply.apply(6, 7));               // 42
Note
`Predicate<T>` also provides default methods `and()`, `or()`, and `negate()` for combining tests, and `Function<T, R>` provides `andThen()` and `compose()` for chaining transformations — these are possible precisely because default methods do not count against the “one abstract method” rule.
  • A functional interface has exactly one abstract method; default and static methods are unlimited.

  • @FunctionalInterface is optional but recommended — it makes accidentally adding a second abstract method a compile error.

  • java.util.function ships common shapes: Function, Predicate, Consumer, Supplier, BiFunction, and more.

  • Lambdas and method references can only target functional interfaces.