The java.util.function package (since Java 8) provides standard functional interfaces used throughout the Stream API and modern Java libraries.

Core Interfaces

Interface Method Description
Function<T,R> R apply(T t) Transform T to R
Consumer<T> void accept(T t) Consume T, return nothing
Supplier<T> T get() Supply a T
Predicate<T> boolean test(T t) Test T, return boolean
BiFunction<T,U,R> R apply(T t, U u) Transform two inputs to R
BiConsumer<T,U> void accept(T t, U u) Consume two values
BiPredicate<T,U> boolean test(T t, U u) Test two values

Examples

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

Consumer<String> print = System.out::println;
print.accept("Hello World");

Supplier<Double> random = Math::random;
System.out.println(random.get());

Predicate<String> isEmpty = String::isEmpty;
System.out.println(isEmpty.test(""));  // true
System.out.println(isEmpty.test("a")); // false
  

Primitive Specializations

Avoid boxing overhead with primitive-specific interfaces:

Interface Type
IntFunction<R> int → R
ToIntFunction<T> T → int
IntPredicate test int
IntConsumer accept int
IntSupplier supply int
ObjIntConsumer<T> accept T and int
  IntUnaryOperator square = x -> x * x;
System.out.println(square.applyAsInt(5)); // 25
  

Operator Interfaces

Specializations of Function where input and output types are the same:

  UnaryOperator<String> upper = String::toUpperCase;
BinaryOperator<Integer> max = Integer::max;

List<Integer> numbers = List.of(3, 1, 4, 1, 5);
Integer result = numbers.stream().reduce(max).orElse(0); // 5
  

Composition Methods

Functional interfaces provide default methods for chaining:

  Function<Integer, Integer> multiplyBy2 = x -> x * 2;
Function<Integer, Integer> add3 = x -> x + 3;

Function<Integer, Integer> combined = multiplyBy2.andThen(add3);
System.out.println(combined.apply(5)); // (5 * 2) + 3 = 13

Function<Integer, Integer> reversed = add3.compose(multiplyBy2);
System.out.println(reversed.apply(5)); // (5 + 3) * 2 = 16

Predicate<String> notEmpty = s -> !s.isEmpty();
Predicate<String> longEnough = s -> s.length() > 5;
Predicate<String> valid = notEmpty.and(longEnough);
  

@FunctionalInterface

Always annotate custom functional interfaces:

  @FunctionalInterface
public interface Validator<T> {
    boolean validate(T value);

    // Default and static methods don't count toward the abstract method limit
    default Validator<T> and(Validator<T> other) {
        return value -> this.validate(value) && other.validate(value);
    }
}
  

Custom vs Built-in

Use built-in interfaces when signatures match. Create custom ones when you need domain-specific names or additional default methods:

  @FunctionalInterface
public interface EventHandler {
    void handle(Event event);
}
  

Best Practices

  • Prefer java.util.function types over custom interfaces when possible
  • Use primitive specializations in performance-critical code
  • Use andThen/compose for readable pipeline composition
  • Annotate custom functional interfaces with @FunctionalInterface