☕ Java

Functional Interfaces

A functional interface is any Java interface that has exactly one abstract method. This single-abstract-method (SAM) contract makes the interface a valid target type for a lambda expression or method reference — the lambda provides the implementation of that one abstract method. The @FunctionalInterface annotation is optional but strongly recommended: it causes the compiler to verify that the interface satisfies the SAM constraint, rejecting it at compile time if there is more than one abstract method. The java.util.function package, introduced in Java 8, provides 43 standard functional interfaces organized around four root types — Function, Consumer, Supplier, Predicate — and their variations for primitives (IntFunction, LongSupplier, DoubleConsumer, etc.), binary operations (BiFunction, BiConsumer, BiPredicate), and unary operators (UnaryOperator, IntUnaryOperator, etc.). This entry covers the design principles behind functional interfaces, the complete @FunctionalInterface contract including default and static methods, the full java.util.function hierarchy and the pattern that governs naming, creating custom functional interfaces with checked exceptions, composing functional interfaces via default methods, and the relationship between functional interfaces and the type system including the rules for lambda assignment and widening.

The SAM Contract, @FunctionalInterface, and Default Methods

The single-abstract-method (SAM) constraint is what makes an interface a functional interface. An interface has exactly one abstract method when all non-abstract methods are either default methods (providing an implementation in the interface), static methods (not abstract), or methods that override public methods from Object (like equals, hashCode, toString — these don't count as abstract even if declared abstract in the interface, because they will be implemented by any concrete class inheriting from Object). @FunctionalInterface is a marker annotation that instructs the compiler to verify the SAM constraint. Without it, any interface that happens to have one abstract method is still a valid functional interface and can be used as a lambda target — @FunctionalInterface is purely a documentation and verification mechanism. With it, the compiler produces an error if the interface has zero or more than one abstract method, preventing accidental violations of the contract. All standard library functional interfaces use @FunctionalInterface. Default methods do not violate the SAM constraint. A functional interface may have many default methods providing composed or specialized operations, as long as it has exactly one abstract method. The java.util.function interfaces make heavy use of this: Function<T,R> has one abstract method apply(T t) and default methods andThen(Function<R,V>), compose(Function<V,T>), and static identity(). Predicate<T> has one abstract method test(T t) and default methods and(Predicate), or(Predicate), negate(), and static isEqual(). Static methods in an interface are also compatible with the SAM constraint. An interface with one abstract method and many static factory/utility methods is still a valid functional interface. The distinction between default and static methods in functional interfaces is: default methods define compositions on instances of the interface (you call them on a Predicate instance to produce another Predicate); static methods are utilities not tied to any instance (Predicate.isEqual(obj) returns a Predicate without requiring an existing Predicate). The inheritance rules for functional interfaces follow the same SAM logic. If interface A extends interface B, and both declare abstract methods, the total count of abstract methods is what matters. If A inherits one abstract method from B and declares another, A has two abstract methods and is not a functional interface. If A inherits one abstract method from B and overrides it (not adding another), A has one abstract method and is functional. If A inherits one abstract method from B and narrows its return type (covariant override), the narrowed version has one abstract method and is functional.
Java
// ── @FunctionalInterface — compiler enforcement ────────────────────────
@FunctionalInterface
interface MySupplier<T> {
    T get();   // the single abstract method

    // Default methods don't violate SAM:
    default MySupplier<T> memoize() {
        T[] value = (T[]) new Object[1];
        boolean[] computed = {false};
        return () -> {
            if (!computed[0]) { value[0] = get(); computed[0] = true; }
            return value[0];
        };
    }

    // Static methods don't violate SAM:
    static <T> MySupplier<T> of(T value) {
        return () -> value;
    }
}

// COMPILE ERROR — two abstract methods:
// @FunctionalInterface
// interface BadInterface {
//     void doThis();
//     void doThat();   // ERROR: multiple non-overriding abstract methods found
// }

// OK — second method overrides Object.equals (not counted as abstract):
@FunctionalInterface
interface StringTransformer {
    String transform(String s);
    @Override boolean equals(Object obj);  // not counted — from Object
}

// ── Inheritance and SAM ────────────────────────────────────────────────
@FunctionalInterface
interface Base {
    void execute();
}

// Child inherits the SAM from Base — still functional:
@FunctionalInterface
interface NamedBase extends Base {
    // No new abstract methods — inherits execute() from Base
    default String name() { return "NamedBase"; }
}

NamedBase nb = () -> System.out.println("executing");  // lambda for execute()
nb.execute();

// NOT functional — adds a second abstract method:
interface TwoMethods extends Base {
    void anotherMethod();   // second abstract method — not functional
}

// ── Standard functional interfaces with default methods ───────────────
// Function.andThen() — compose two functions sequentially:
Function<String, Integer> length  = String::length;
Function<Integer, String> intToStr = i -> "Length: " + i;
Function<String, String>  composed = length.andThen(intToStr);
System.out.println(composed.apply("hello"));  // "Length: 5"

// Function.compose() — compose in reverse order:
Function<String, String> composed2 = intToStr.compose(length);
System.out.println(composed2.apply("world"));  // "Length: 5"

// Predicate.and(), or(), negate():
Predicate<String> notEmpty    = s -> !s.isEmpty();
Predicate<String> longEnough  = s -> s.length() >= 3;
Predicate<String> validInput  = notEmpty.and(longEnough);
Predicate<String> anyOk       = notEmpty.or(longEnough);
Predicate<String> isEmpty     = notEmpty.negate();

System.out.println(validInput.test("hi"));    // false (too short)
System.out.println(validInput.test("hello")); // true
System.out.println(isEmpty.test(""));         // true

// Function.identity() — no-op transformation:
Function<String, String> identity = Function.identity();
System.out.println(identity.apply("unchanged"));  // "unchanged"

The java.util.function Hierarchy — All 43 Standard Interfaces

The java.util.function package provides 43 standard functional interfaces organized by four properties: the number of parameters (nullary, unary, binary), the presence of a return value, the type of inputs and outputs (generic vs primitive), and whether inputs and outputs are the same type. The four root types govern the design: Function<T,R> takes one argument and returns a result; Consumer<T> takes one argument and returns nothing; Supplier<T> takes no arguments and returns a result; Predicate<T> takes one argument and returns boolean. The Binary prefix adds a second argument of the same type as the first: BiFunction<T,U,R>, BiConsumer<T,U>, BiPredicate<T,U>. There is no BiSupplier because a supplier takes no arguments by definition. The Unary and Binary Operator specializations handle the case where input and output types are the same: UnaryOperator<T> extends Function<T,T> (takes T, returns T), BinaryOperator<T> extends BiFunction<T,T,T> (takes two T, returns T). These are used for operations that transform values within the same type — String::toUpperCase as UnaryOperator<String>, Integer::sum as BinaryOperator<Integer>. Primitive specializations exist for int, long, and double (not byte, short, float — these use widening). IntFunction<R> (int → R), LongFunction<R>, DoubleFunction<R>. ToIntFunction<T> (T → int), ToLongFunction<T>, ToDoubleFunction<T>. IntToLongFunction (int → long), IntToDoubleFunction, LongToIntFunction, LongToDoubleFunction, DoubleToIntFunction, DoubleToLongFunction. IntConsumer, LongConsumer, DoubleConsumer. IntSupplier, LongSupplier, DoubleSupplier. IntPredicate, LongPredicate, DoublePredicate. IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator. IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator. ToIntBiFunction<T,U>, ToLongBiFunction<T,U>, ToDoubleBiFunction<T,U>. The ObjXxxConsumer specializations handle the mixed case where one argument is generic and one is primitive: ObjIntConsumer<T>, ObjLongConsumer<T>, ObjDoubleConsumer<T>. These arise naturally in collectors and fold operations. Primitive interfaces exist to avoid boxing and unboxing overhead. When a stream contains int values and maps to int values, using IntUnaryOperator instead of Function<Integer, Integer> avoids boxing 32-bit ints into Integer objects — which costs object allocation and cache pressure. For performance-sensitive code processing numeric data, always use primitive functional interfaces.
Java
// ── The four root types ───────────────────────────────────────────────
// Function<T,R>: T → R
Function<String, Integer> strLen = String::length;         // Stringint
Function<Integer, String> intStr = Object::toString;       // IntegerString
System.out.println(strLen.apply("hello"));  // 5
System.out.println(intStr.apply(42));       // "42"

// Consumer<T>: T → void
Consumer<String> printer = System.out::println;
Consumer<String> logger  = s -> System.err.println("[LOG] " + s);
Consumer<String> both    = printer.andThen(logger);  // sequential composition
both.accept("message");  // prints to stdout and stderr

// Supplier<T>: () → T
Supplier<List<String>> listMaker = ArrayList::new;
Supplier<Instant>      now       = Instant::now;
System.out.println(listMaker.get().getClass().getSimpleName());  // ArrayList
System.out.println(now.get());   // current instant

// Predicate<T>: T → boolean
Predicate<String> blank   = String::isBlank;
Predicate<String> longStr = s -> s.length() > 10;
System.out.println(blank.test("   "));      // true
System.out.println(longStr.test("hello"));  // false

// ── Binary variants ───────────────────────────────────────────────────
BiFunction<String, Integer, String> repeat = (s, n) ->
    s.repeat(n);
System.out.println(repeat.apply("ab", 3));   // "ababab"

BiConsumer<String, Integer> printTimes = (s, n) -> {
    for (int i = 0; i < n; i++) System.out.print(s);
    System.out.println();
};
printTimes.accept("*", 5);  // *****

BiPredicate<String, String> startsWith = String::startsWith;
System.out.println(startsWith.test("hello", "hel"));  // true

// ── Operator specializations ──────────────────────────────────────────
UnaryOperator<String>  upper   = String::toUpperCase;
BinaryOperator<String> concat  = String::concat;
BinaryOperator<Integer> max    = Integer::max;

System.out.println(upper.apply("hello"));          // HELLO
System.out.println(concat.apply("foo", "bar"));    // foobar
System.out.println(max.apply(3, 7));               // 7

// BinaryOperator.minBy() and maxBy() — useful for reduce():
List<String> words = List.of("apple", "banana", "cherry", "date");
Optional<String> longest = words.stream()
    .reduce(BinaryOperator.maxBy(Comparator.comparingInt(String::length)));
System.out.println(longest.orElse("none"));  // banana or cherry (6 chars)

// ── Primitive specializations — avoiding boxing ────────────────────────
// WITHOUT primitive interface — boxes every int:
Function<Integer, Integer> squareBoxed = n -> n * n;
int result1 = squareBoxed.apply(5);   // autoboxes 5Integer, result → int

// WITH primitive interface — no boxing:
IntUnaryOperator squarePrimitive = n -> n * n;
int result2 = squarePrimitive.applyAsInt(5);  // pure int arithmetic

// Benchmark impact for processing arrays:
int[] numbers = IntStream.rangeClosed(1, 1_000_000).toArray();

// With boxing (IntStream → boxed → unboxed):
long sum1 = Arrays.stream(numbers).boxed()
    .map(n -> n * n)          // Function<Integer,Integer>: boxing
    .mapToLong(Integer::longValue)
    .sum();

// Without boxing (all primitives):
long sum2 = Arrays.stream(numbers)
    .map(n -> n * n)          // IntUnaryOperator: no boxing
    .asLongStream()
    .sum();
// sum2 is significantly faster for large arrays

// ── ObjXxxConsumer — mixed generic+primitive ─────────────────────────
ObjIntConsumer<StringBuilder> appendN = (sb, n) -> sb.append(n);
StringBuilder sb = new StringBuilder();
appendN.accept(sb, 42);
appendN.accept(sb, 7);
System.out.println(sb);   // "427"

// Common in streams for indexed operations:
List<String> items = List.of("a", "b", "c");
ObjIntConsumer<String> indexedPrint = (s, i) -> System.out.println(i + ": " + s);
for (int i = 0; i < items.size(); i++) {
    indexedPrint.accept(items.get(i), i);
}

Custom Functional Interfaces, Checked Exceptions, and Composition Patterns

The standard java.util.function interfaces cover most use cases, but custom functional interfaces are appropriate when the signature does not match any standard interface, when checked exceptions must be declared, when the business meaning should be encoded in the interface name, or when additional default methods for domain-specific composition are needed. A custom functional interface is simply an interface annotated @FunctionalInterface with one abstract method. The name is its documentation: Transformer<T> communicates that it transforms values, where Function<T,T> communicates nothing specific. OrderValidator<T extends Order> communicates that it validates orders, where Predicate<T> does not. The checked exception problem with standard interfaces is one of the most common motivations for custom interfaces. Function<T,R> cannot throw IOException — adding a checked exception to a lambda that implements Function<T,R> is a compile error. A custom CheckedFunction<T,R> or IOFunction<T,R> that declares the checked exception allows lambdas to propagate it naturally. The trade-off is that the custom interface doesn't integrate with standard library APIs (stream methods accept Function, not CheckedFunction) without adapter methods. Adapter patterns for converting between checked and unchecked interfaces are common in production code. The wrap() pattern (static <T,R> Function<T,R> wrap(CheckedFunction<T,R> f)) converts a checked-throwing function to an unchecked-throwing Function. Libraries like Vavr provide CheckedFunction0 through CheckedFunction8 with built-in try/unchecked conversion. This pattern should be applied at the boundary where checked exceptions are converted to unchecked, not scattered throughout the codebase. Functional interface composition can be implemented as default methods to create domain-specific DSLs. A Validator<T> that combines validations with and() and or() default methods creates a clear API for composing validation rules. A Pipeline<T> that chains transformations with then() creates a readable processing pipeline. These patterns emerge naturally when the functional interface's domain has obvious composition semantics.
Java
// ── Custom functional interface — when standard ones don't fit ─────────

// Better semantics than Function<Order, ValidationResult>:
@FunctionalInterface
interface OrderValidator {
    ValidationResult validate(Order order);

    // Default composition:
    default OrderValidator and(OrderValidator other) {
        return order -> {
            ValidationResult r1 = this.validate(order);
            return r1.isValid() ? other.validate(order) : r1;
        };
    }

    default OrderValidator or(OrderValidator other) {
        return order -> {
            ValidationResult r1 = this.validate(order);
            return r1.isValid() ? r1 : other.validate(order);
        };
    }
}

// Usage reads like English:
OrderValidator notEmpty  = order -> order.items().isEmpty()
    ? ValidationResult.error("Order is empty")
    : ValidationResult.ok();

OrderValidator notExpired = order -> order.expiresAt().isBefore(Instant.now())
    ? ValidationResult.error("Order expired")
    : ValidationResult.ok();

OrderValidator combined = notEmpty.and(notExpired);
ValidationResult result = combined.validate(someOrder);

// ── Custom interface with checked exception ────────────────────────────
@FunctionalInterface
interface IOFunction<T, R> {
    R apply(T t) throws IOException;

    // Adapter to unchecked Function:
    default Function<T, R> unchecked() {
        return t -> {
            try { return apply(t); }
            catch (IOException e) { throw new UncheckedIOException(e); }
        };
    }

    // Static factory — converts checked to unchecked at the boundary:
    static <T, R> Function<T, R> wrap(IOFunction<T, R> f) {
        return f.unchecked();
    }
}

// Lambda can throw IOException:
IOFunction<Path, String> reader = Files::readString;
String content = reader.apply(Path.of("file.txt"));  // can throw IOException

// Used in stream with unchecked adapter:
List<Path> paths = List.of(Path.of("a.txt"), Path.of("b.txt"));
List<String> contents = paths.stream()
    .map(IOFunction.wrap(Files::readString))  // checked → unchecked at boundary
    .collect(Collectors.toList());

// ── Pipeline with fluent default methods ──────────────────────────────
@FunctionalInterface
interface Transformer<T> {
    T transform(T input);

    default Transformer<T> then(Transformer<T> next) {
        return input -> next.transform(this.transform(input));
    }

    static <T> Transformer<T> identity() {
        return input -> input;
    }
}

Transformer<String> trim      = String::trim;
Transformer<String> normalize = s -> s.replaceAll("\s+", " ");
Transformer<String> lower     = String::toLowerCase;
Transformer<String> pipeline  = trim.then(normalize).then(lower);

System.out.println(pipeline.transform("  Hello   World  "));  // "hello world"

// ── Converting between similar functional interfaces ───────────────────
// Function<T,Boolean> vs Predicate<T> — logically same, different API
Function<String, Boolean> funcPred = String::isEmpty;
Predicate<String> pred = String::isEmpty;

// Convert Function<T,Boolean> to Predicate<T>:
Predicate<String> fromFunc = funcPred::apply;   // method reference adapts
System.out.println(fromFunc.test(""));   // true

// Convert Predicate<T> to Function<T,Boolean>:
Function<String, Boolean> fromPred = pred::test;
System.out.println(fromPred.apply(""));  // true

// ── Functional interface assignability rules ───────────────────────────
// A lambda can be assigned to ANY functional interface with a matching signature:
Runnable r     = () -> System.out.println("hello");   // () → void
Callable<Void> c = () -> { System.out.println("hello"); return null; };  // () → Void

// Same lambda body, different interface types — both legal:
// The compiler checks structural compatibility, not nominal types
Supplier<String>  s1 = () -> "hello";
Callable<String>  s2 = () -> "hello";   // Callable<V> has V call() — same signature
// s1 and s2 have the same lambda body but different types — not assignable to each other

// No widening — lambda types don't form a hierarchy:
// Supplier<String> s3 = s2;   // COMPILE ERROR: Callable is not Supplier

// Cast works only when structurally compatible AND you know the runtime type:
// Generally avoid casting lambdas — use explicit assignment to target type

Related Topics in Java 8 Features

Lambda Expressions
Lambda expressions, introduced in Java 8, are anonymous functions — blocks of code that can be stored in variables, passed as arguments, and returned from methods, treating behavior as data. A lambda has three parts: a parameter list, an arrow token (->), and a body. The body is either a single expression (whose value is the implicit return value) or a block of statements wrapped in braces. Lambdas implement functional interfaces — interfaces with exactly one abstract method — allowing any lambda whose signature matches the abstract method's signature to be used wherever that interface is expected. The lambda syntax is syntactic sugar: every lambda is compiled to an invocation of the functional interface's abstract method, with the compiler generating a class (via invokedynamic) that implements the interface and delegates to the lambda body. This entry covers the complete lambda syntax including all shorthand forms, variable capture and the effectively-final constraint, method references as a specialized lambda syntax, the relationship between lambdas and the type system, how lambdas interact with exception handling, the invokedynamic compilation strategy and its performance characteristics, and the complete set of rules governing lambda type inference.
Predicate
Predicate<T> is a functional interface in java.util.function representing a boolean-valued function of one argument, with the single abstract method boolean test(T t). It is one of the four foundational functional interfaces in the Java standard library and is used throughout the Collections framework, Streams API, and Optional for filtering, condition testing, and validation. Predicate is designed for composition: its default methods and(Predicate), or(Predicate), and negate() allow building complex boolean expressions from simple predicates without boilerplate. The static methods isEqual(Object) and not(Predicate) provide factory methods for common cases. The primitive specializations IntPredicate, LongPredicate, and DoublePredicate avoid boxing overhead for numeric values. BiPredicate<T,U> extends the concept to two-argument boolean functions. This entry covers the complete Predicate API, all composition methods and their short-circuit semantics, the static factory methods, primitive specializations, BiPredicate, using Predicate in stream pipelines and Collections methods, building validation frameworks with Predicate composition, and the performance and readability trade-offs of different composition styles.
Function
Function<T,R> is a functional interface in java.util.function representing a function that accepts one argument of type T and produces a result of type R, with the single abstract method R apply(T t). It is the most general transformation interface in the standard library, used throughout the Streams API for mapping (Stream.map()), in Optional for value transformation (Optional.map(), Optional.flatMap()), and as a building block for more specialized functional interfaces. Function provides two default composition methods — andThen() and compose() — that create new functions by chaining two functions together, enabling functional pipeline construction without intermediate variables. The specializations cover all combinations of generic and primitive inputs and outputs: ToIntFunction, IntFunction, IntToLongFunction, and so on. UnaryOperator<T> extends Function<T,T> for operations that transform a value within the same type. BiFunction<T,U,R> generalizes to two input arguments. This entry covers the complete Function API, the semantics of andThen versus compose, all specializations and when each is appropriate, the functional relationship between Function and other java.util.function types, partial application patterns, and Function as the basis for building data pipelines.
Consumer
Consumer<T> is a functional interface in java.util.function that represents an operation that accepts a single input argument and returns no result. It is the standard abstraction for side-effect-oriented operations — printing, logging, persisting, modifying in-place, or any action that consumes a value without producing a new one. Consumer<T> declares one abstract method, accept(T t), and one default method, andThen(Consumer<? super T> after), which composes two consumers sequentially: the second consumer receives the same input as the first, enabling chaining of multiple side effects on the same value. The java.util.function package also provides primitive specializations — IntConsumer, LongConsumer, DoubleConsumer — that avoid boxing overhead, and BiConsumer<T, U> for operations on two arguments. Consumer is used throughout the Stream API (forEach), the Collections API (Iterable.forEach, Map.forEach), and is the standard type for callback and event-handler parameters. This entry covers the full Consumer API including andThen composition, all primitive specializations and BiConsumer, the distinction between Consumer and other functional interfaces, common usage patterns in streams and collections, and how to write effective Consumer implementations.