7.1 Built-in Functional Interfaces in java.util.function

Key Takeaways

  • A functional interface defines exactly one abstract method (Single Abstract Method or SAM contract), while permitting any number of default, static, and Object-inherited public methods.
  • The four foundational functional interface families are Supplier<T> (T get()), Consumer<T> (void accept(T)), Predicate<T> (boolean test(T)), and Function<T, R> (R apply(T)).
  • Bi-variants accept two arguments: BiConsumer<T, U>, BiPredicate<T, U>, and BiFunction<T, U, R>; there is no BiSupplier because methods in Java cannot return two distinct values.
  • Primitive specializations (such as IntPredicate, ToLongFunction<T>, DoubleSupplier, and IntToDoubleFunction) optimize performance by eliminating autoboxing and unboxing overhead.
Last updated: September 2026

Built-in Functional Interfaces in java.util.function

Functional programming in Java revolves around functional interfaces—interfaces that declare exactly one abstract method. Introduced in Java 8 and continuously leveraged across modern Java standard APIs, functional interfaces act as target types for lambda expressions and method references. For the Oracle Certified Professional: Java SE 21 Developer (1Z0-830) exam, you must demonstrate comprehensive mastery of the java.util.function package, including core families, binary variants, primitive specializations, operator sub-interfaces, and composition combinators.


1. Functional Interfaces & The SAM Contract

A functional interface is defined by its Single Abstract Method (SAM) contract. An interface is a valid functional interface if and only if it contains exactly one abstract method.

The @FunctionalInterface Annotation

The @FunctionalInterface annotation is optional, but it instructs the Java compiler to verify that the annotated interface strictly adheres to the SAM contract. If an annotated interface declares zero abstract methods or more than one abstract method, the compiler generates an error.

@FunctionalInterface
public interface StringTransformer {
    String transform(String input); // Single Abstract Method (SAM)
    
    // Default methods do NOT count toward SAM count
    default String transformTwice(String input) {
        return transform(transform(input));
    }
    
    // Static methods do NOT count toward SAM count
    static StringTransformer identity() {
        return s -> s;
    }
    
    // Public methods matching java.lang.Object signatures do NOT count toward SAM count
    @Override
    boolean equals(Object obj);
    
    @Override
    String toString();
}

[!IMPORTANT] Methods Matching java.lang.Object: Any public method declared in an interface that overrides a public method of java.lang.Object (such as boolean equals(Object), int hashCode(), or String toString()) does not count toward the interface's abstract method count. This is because every implementing class automatically inherits an implementation from java.lang.Object.


2. The Four Core Functional Interface Families

All 43 functional interfaces in java.util.function derive from or specialize four foundational interface archetypes:

+-------------------------------------------------------------------------------+
|                   THE FOUR CORE FUNCTIONAL FAMILIES                           |
|                                                                               |
|  1. Supplier<T>      : () -> T           [Produces data, takes nothing]       |
|  2. Consumer<T>      : (T) -> void       [Consumes data, returns nothing]     |
|  3. Predicate<T>     : (T) -> boolean    [Evaluates boolean condition]        |
|  4. Function<T, R>   : (T) -> R          [Transforms input T to output R]     |
+-------------------------------------------------------------------------------+

1. Supplier<T>

  • SAM: T get()
  • Purpose: Generates or supplies an instance of type T without requiring any input parameters.
  • Common Use: Lazy evaluation, factory methods, default value generation.
Supplier<LocalDate> todaySupplier = () -> LocalDate.now();
Supplier<List<String>> listFactory = ArrayList::new;
LocalDate now = todaySupplier.get();

2. Consumer<T>

  • SAM: void accept(T t)
  • Purpose: Accepts a single input argument of type T and performs a side-effect operation, returning no result (void).
  • Default Method: default Consumer<T> andThen(Consumer<? super T> after) executes the current consumer followed by the after consumer.
Consumer<String> printer = s -> System.out.print("[" + s + "]");
Consumer<String> logger = s -> System.out.println(" Logged: " + s.length());
Consumer<String> pipeline = printer.andThen(logger);
pipeline.accept("Java 21"); // Prints: [Java 21] Logged: 7

3. Predicate<T>

  • SAM: boolean test(T t)
  • Purpose: Evaluates a condition against an argument of type T, returning a primitive boolean.
  • Default Methods:
    • default Predicate<T> and(Predicate<? super T> other) (Logical AND with short-circuit evaluation)
    • default Predicate<T> or(Predicate<? super T> other) (Logical OR with short-circuit evaluation)
    • default Predicate<T> negate() (Logical NOT)
  • Static Factory Methods:
    • static <T> Predicate<T> isEqual(Object targetRef) (Uses Objects.equals)
    • static <T> Predicate<T> not(Predicate<? super T> target) (Java 11+ helper for method references)
Predicate<String> isNonEmpty = s -> s != null && !s.isBlank();
Predicate<String> isShort = s -> s.length() < 10;
Predicate<String> validShortName = isNonEmpty.and(isShort);

// Java 11+ Predicate.not with method references
List<String> items = List.of("A", "", "B", "   ");
long count = items.stream().filter(Predicate.not(String::isBlank)).count(); // 2

4. Function<T, R>

  • SAM: R apply(T t)
  • Purpose: Accepts an argument of type T and computes a result of type R.
  • Default Methods:
    • default <V> Function<T, V> andThen(Function<? super R, ? extends V> after): Evaluates this first, then passes the result to after.
    • default <V> Function<V, R> compose(Function<? super V, ? extends T> before): Evaluates before first, then passes the result to this.
  • Static Method: static <T> Function<T, T> identity() returns a function that always returns its input argument.
Function<String, Integer> parse = Integer::parseInt;
Function<Integer, Integer> square = x -> x * x;

// andThen: parse first, then square: parse(s) -> square(result)
Function<String, Integer> parseAndSquare = parse.andThen(square);
int res1 = parseAndSquare.apply("5"); // 25

// compose: square first, then parse: square(x) -> parse(result) - Requires compatible types
Function<Integer, Integer> multiplyByTwo = x -> x * 2;
Function<Integer, Integer> squareThenDouble = multiplyByTwo.compose(square);
int res2 = squareThenDouble.apply(3); // square(3)=9, multiplyByTwo(9)=18

3. Binary Functional Interfaces

When operations require two input arguments, Java provides binary functional interface variants. Notice that there is no BiSupplier in Java because a method cannot return multiple independent return values.

InterfaceSAM SignatureReturn TypeKey Methods
BiConsumer<T, U>void accept(T t, U u)voidandThen(BiConsumer)
BiPredicate<T, U>boolean test(T t, U u)booleanand(), or(), negate()
BiFunction<T, U, R>R apply(T t, U u)RandThen(Function)

[!WARNING] BiFunction has NO compose() method: While Function<T, R> provides both compose() and andThen(), BiFunction<T, U, R> provides only andThen(Function<? super R, ? extends V> after). It does not support compose() because a preceding function would need to produce two return values simultaneously to feed T and U into BiFunction.


4. Operator Interfaces: UnaryOperator & BinaryOperator

Operators are specializations of Function and BiFunction where the input argument types and return type are identical.

// UnaryOperator<T> extends Function<T, T>
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

// BinaryOperator<T> extends BiFunction<T, T, T>
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
    static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
    }
    
    static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
    }
}
UnaryOperator<String> toUpper = String::toUpperCase;
String result = toUpper.apply("java 21"); // "JAVA 21"

BinaryOperator<Integer> adder = (a, b) -> a + b;
int sum = adder.apply(10, 20); // 30

BinaryOperator<String> longest = BinaryOperator.maxBy(Comparator.comparingInt(String::length));
String winner = longest.apply("Elephant", "Cat"); // "Elephant"

5. Primitive Functional Interface Specializations

To avoid the severe memory and CPU performance overhead of boxing and unboxing primitives (int $\leftrightarrow$ Integer, double $\leftrightarrow$ Double, long $\leftrightarrow$ Long), java.util.function includes specialized interfaces for the three primary numerical primitive types: int, long, and double (plus BooleanSupplier).

1. Primitive Suppliers, Consumers, and Predicates

InterfaceSAM SignatureMethod Name
IntSupplierint getAsInt()getAsInt()
LongSupplierlong getAsLong()getAsLong()
DoubleSupplierdouble getAsDouble()getAsDouble()
BooleanSupplierboolean getAsBoolean()getAsBoolean()
IntConsumervoid accept(int value)accept(int)
LongConsumervoid accept(long value)accept(long)
DoubleConsumervoid accept(double value)accept(double)
IntPredicateboolean test(int value)test(int)
LongPredicateboolean test(long value)test(long)
DoublePredicateboolean test(double value)test(double)

2. Primitive Function Categories

Category A: Takes Primitive, Returns Object (XFunction<R>)

  • IntFunction<R>: R apply(int value)
  • LongFunction<R>: R apply(long value)
  • DoubleFunction<R>: R apply(double value)

Category B: Takes Object, Returns Primitive (ToXFunction<T>)

  • ToIntFunction<T>: int applyAsInt(T value)
  • ToLongFunction<T>: long applyAsLong(T value)
  • ToDoubleFunction<T>: double applyAsDouble(T value)

Category C: Takes Primitive, Returns Primitive (XToYFunction - 9 combinations)

  • IntToLongFunction: long applyAsLong(int value)
  • IntToDoubleFunction: double applyAsDouble(int value)
  • LongToIntFunction: int applyAsInt(long value)
  • LongToDoubleFunction: double applyAsDouble(long value)
  • DoubleToIntFunction: int applyAsInt(double value)
  • DoubleToLongFunction: long applyAsLong(double value)
  • IntUnaryOperator: int applyAsInt(int operand)
  • LongUnaryOperator: long applyAsLong(long operand)
  • DoubleUnaryOperator: double applyAsDouble(double operand)

Category D: Two-Argument Primitive Variants

  • ObjIntConsumer<T>: void accept(T t, int value)
  • ObjLongConsumer<T>: void accept(T t, long value)
  • ObjDoubleConsumer<T>: void accept(T t, double value)
  • ToIntBiFunction<T, U>: int applyAsInt(T t, U u)
  • ToLongBiFunction<T, U>: long applyAsLong(T t, U u)
  • ToDoubleBiFunction<T, U>: double applyAsDouble(T t, U u)
  • IntBinaryOperator: int applyAsInt(int left, int right)
  • LongBinaryOperator: long applyAsLong(long left, long right)
  • DoubleBinaryOperator: double applyAsDouble(double left, double right)
// Primitive Specializations in Practice
IntToDoubleFunction half = x -> x / 2.0;
double halfResult = half.applyAsDouble(7); // 3.5

ObjIntConsumer<List<String>> listAppender = (list, index) -> list.add("Item #" + index);
List<String> storage = new ArrayList<>();
listAppender.accept(storage, 42); // storage contains ["Item #42"]

ToIntFunction<String> strLength = String::length;
int len = strLength.applyAsInt("Oracle Java 21"); // 14

6. Comprehensive Summary Table for the 1Z0-830 Exam

Interface NameNumber of ArgsSAM Method NameReturn TypeBoxed or Primitive
Supplier<T>0get()TReference
BooleanSupplier0getAsBoolean()booleanPrimitive
IntSupplier0getAsInt()intPrimitive
Consumer<T>1accept(T t)voidReference
IntConsumer1accept(int v)voidPrimitive
BiConsumer<T, U>2accept(T t, U u)voidReference
ObjIntConsumer<T>2accept(T t, int v)voidHybrid
Predicate<T>1test(T t)booleanReference input
IntPredicate1test(int v)booleanPrimitive input
BiPredicate<T, U>2test(T t, U u)booleanReference inputs
Function<T, R>1apply(T t)RReference
IntFunction<R>1apply(int v)RPrimitive input
ToIntFunction<T>1applyAsInt(T t)intPrimitive output
IntToDoubleFunction1applyAsDouble(int v)doublePrimitive to Primitive
BiFunction<T, U, R>2apply(T t, U u)RReference
ToIntBiFunction<T, U>2applyAsInt(T t, U u)intPrimitive output
UnaryOperator<T>1apply(T t)TReference
IntUnaryOperator1applyAsInt(int v)intPrimitive
BinaryOperator<T>2apply(T t1, T t2)TReference
IntBinaryOperator2applyAsInt(int a, int b)intPrimitive
Loading diagram...
Functional Interface Taxonomy and Composition Combinators
Test Your Knowledge

Which of the following interfaces is a valid functional interface that compiles without error when annotated with @FunctionalInterface?

A
B
C
D
Test Your Knowledge

Given the following functional expressions: Function<Integer, Integer> multiplyByThree = x -> x * 3; Function<Integer, Integer> addFive = x -> x + 5; Function<Integer, Integer> combined = multiplyByThree.compose(addFive); int result = combined.apply(2); What is the resulting value of result?

A
B
C
D
Test Your Knowledge

Which of the following functional interfaces correctly maps an int primitive input directly to a double primitive output without boxing, and what is its Single Abstract Method name?

A
B
C
D
Test Your Knowledge

Examine the following code snippet utilizing java.util.function interfaces: BiPredicate<String, Integer> lengthChecker = (s, len) -> s.length() == len; BiPredicate<String, Integer> nonNull = (s, len) -> s != null; BiPredicate<String, Integer> validator = nonNull.and(lengthChecker.negate()); boolean res = validator.test("Java21", 6); What is the value of res?

A
B
C
D