java.util.function Package
Master java.util.function: Predicate, Function, Supplier, Consumer, and their binary/triple variants for functional-style Java.
Introduction
The java.util.function package (added in Java 8) provides purpose-built functional interfaces that serve as the backbone of the Stream API, lambda expressions, and method references. Rather than forcing developers to create custom single-method interfaces, the JDK ships a standardized set that covers the most common transformation patterns.
When to Use
| Interface | Signature | Use Case |
|---|---|---|
Predicate<T> | T → boolean | Filter conditions, validation rules |
Function<T,R> | T → R | Transform one type to another |
Supplier<T> | () → T | Lazy value production, factory methods |
Consumer<T> | T → void | Side-effect operations, forEach |
BinaryOperator<T> | (T, T) → T | Combining two values of same type |
UnaryOperator<T> | T → T | In-place transformation, identity operations |
BiPredicate<T,U> | (T, U) → boolean | Two-argument filter conditions |
BiFunction<T,U,R> | (T, U) → R | Two-argument transformations |
BiConsumer<T,U> | (T, U) → void | Two-argument side effects |
When NOT to Use
- Long-running IO operations: Functional interfaces are synchronous by design. Use
CompletableFutureor reactive types (Flux,Mono) for async work. - Checked exceptions: The functional interfaces do not declare checked exceptions. Wrap in unchecked exceptions or use a
Function<T, R>that re-throws. - Stateful predicates in streams: A
Predicateused as a stream filter should be stateless and pure — stateful predicates produce non-deterministic results when used in parallel pipelines.
Interface Taxonomy
classDiagram
class Predicate~T~ {
<<@FunctionalInterface>>
+test(T) boolean
+and(Predicate) Predicate
+or(Predicate) Predicate
+negate() Predicate
+isEqual(Object) Predicate
}
class Function~T,R~ {
<<@FunctionalInterface>>
+apply(T) R
+andThen(Function) Function
+compose(Function) Function
+identity() Function
}
class Supplier~T~ {
<<@FunctionalInterface>>
+get() T
}
class Consumer~T~ {
<<@FunctionalInterface>>
+accept(T) void
+andThen(Consumer) Consumer
}
class BinaryOperator~T~ {
<<@FunctionalInterface>>
+apply(T, T) T
+minBy(Comparator) BinaryOperator
+maxBy(Comparator) BinaryOperator
}
class UnaryOperator~T~ {
<<@FunctionalInterface>>
+apply(T) T
}
Predicate ..|> Object
Function ..|> Object
Supplier ..|> Object
Consumer ..|> Object
BinaryOperator --|> BiFunction
UnaryOperator --|> Function
Code Examples
Predicate — Filtering and Conditions
import java.util.function.Predicate;
import java.util.List;
Predicate<String> isNonBlank = s -> !s.isBlank();
Predicate<String> hasAtSymbol = s -> s.contains("@");
Predicate<String> isValidEmail = isNonBlank.and(hasAtSymbol);
// negate
Predicate<String> isInvalidEmail = isValidEmail.negate();
// Combining with or
Predicate<String> isAdminOrUser = s -> s.startsWith("admin_")
.or(s -> s.startsWith("user_"));
// static isEqual
Predicate<Object> isNull = Predicate.isEqual(null);
Predicate<String> isHello = Predicate.isEqual("hello");
// Using in stream filter
List<String> emails = List.of("alice@example.com", "bob", "charlie@test.com");
List<String> valid = emails.stream()
.filter(isValidEmail)
.toList(); // [alice@example.com, charlie@test.com]
Function — Transformations
import java.util.function.Function;
import java.util.List;
Function<String, Integer> length = String::length;
Function<Integer, String> repeat = n -> "*".repeat(n);
// andThen — apply this first, then the other
Function<String, String> label = length.andThen(repeat);
// compose — apply the other first, then this
Function<String, Integer> totalStars = repeat.compose(length.compose(repeat));
// identity
Function<String, String> identity = Function.identity();
// Chain multiple transforms
record User(Long id, String name, String department) {}
List<User> users = List.of(
new User(1L, "Alice", "Engineering"),
new User(2L, "Bob", "Marketing")
);
List<String> deptNames = users.stream()
.map(u -> u.department())
.map(String::toUpperCase)
.distinct()
.toList(); // [ENGINEERING, MARKETING]
Supplier — Lazy Evaluation
import java.util.function.Supplier;
// Lazy default value — only computed when absent
Supplier<Connection> connectionSupplier = () -> createExpensiveConnection();
Connection conn = Optional.ofNullable(cachedConnection)
.orElseGet(connectionSupplier);
// Factory pattern
Supplier<LocalDate> todaySupplier = LocalDate::now;
// Singleton pattern
Supplier<List<String>> listSupplier = () -> new ArrayList<>(); // new list each time
// For constant list
Supplier<List<String>> constantList = List::of; // same list returned every time
Consumer — Side Effects
import java.util.function.Consumer;
// forEach with Consumer
Consumer<String> printer = System.out::println;
List.of("a", "b", "c").forEach(printer);
// andThen chain
Consumer<String> upperPrinter = s -> System.out.println(s.toUpperCase());
Consumer<String> withTimestamp = s -> System.out.println("[INFO] " + s);
Consumer<String> combined = upperPrinter.andThen(withTimestamp);
combined.accept("hello"); // prints HELLO then [INFO] hello
// BiConsumer
BiConsumer<String, Integer> kvPrinter = (k, v) -> System.out.println(k + "=" + v);
kvPrinter.accept("score", 42); // score=42
BinaryOperator and UnaryOperator
import java.util.function.BinaryOperator;
import java.util.Arrays;
// BinaryOperator — combine two same-typed values
BinaryOperator<Integer> sum = Integer::sum;
BinaryOperator<Integer> max = Integer::max;
BinaryOperator<Integer> min = Integer::min;
// Using with reduce
int total = Arrays.asList(1, 2, 3, 4, 5).stream()
.reduce(0, sum); // 15
// minBy / maxBy
BinaryOperator<Integer> youngest = BinaryOperator.minBy(Comparator.comparingInt(User::getAge));
BinaryOperator<Integer> oldest = BinaryOperator.maxBy(Comparator.comparingInt(User::getAge));
// UnaryOperator — transform a value to same type
UnaryOperator<String> toTitleCase = s ->
s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
UnaryOperator<String> addPrefix = s -> "ID-" + s;
UnaryOperator<String> composed = addPrefix.compose(toTitleCase);
composed.apply("alice"); // "ID-Alice"
Primitive-Focused Specializations
import java.util.function.IntPredicate;
import java.util.function.IntFunction;
import java.util.function.IntSupplier;
import java.util.function.IntConsumer;
import java.util.function.ToIntFunction;
// IntPredicate — avoids boxing
IntPredicate isEven = n -> n % 2 == 0;
boolean result = isEven.test(42); // true
// ToIntFunction — converts any type to int
ToIntFunction<String> strLength = String::length;
strLength.applyAsInt("hello"); // 5
// IntSupplier — generates primitive ints
IntSupplier randomInt = () -> new Random().nextInt(100);
// IntFunction — takes int, returns any type
IntFunction<String> repeatStars = n -> "*".repeat(n);
repeatStars.apply(3); // "***"
// IntConsumer
IntConsumer printInt = System.out::println;
IntStream.range(1, 4).forEach(printInt); // 1, 2, 3
Failure Scenarios
| Scenario | Problem | Solution |
|---|---|---|
| Stateful predicate in parallel stream | Non-deterministic results | Use stateless, pure predicates |
| Checked exception in lambda | Lambda cannot throw checked exceptions | Wrap in unchecked exception or use a wrapper utility |
Function.identity() in map with null value | NPE if the function returns null | Use explicit (s) -> s instead of Function.identity() |
Consumer.andThen when first throws | Second consumer never runs | Log and handle errors before chaining |
IntFunction returning null for non-primitive result | Boxing ambiguity | Use Function<Integer, R> if null is a valid return |
Trade-off Table
| Aspect | Custom Interface | java.util.function |
|---|---|---|
| Reusability | May be scattered across codebase | Standardized, predictable semantics |
| API familiarity | Requires documentation | Self-documenting by type name |
| Compatibility with Streams | Requires adapter | Native compatibility |
| Naming clarity | Domain-specific names | Generic names (Predicate, Function, etc.) |
Observability Checklist
// Wrapping functions for observability
public <T, R> Function<T, R> observedFunction(String name, Function<T, R> fn) {
return t -> {
long start = System.nanoTime();
try {
R result = fn.apply(t);
System.out.println("metric=" + name + " duration_ns=" + (System.nanoTime() - start));
return result;
} catch (RuntimeException e) {
System.out.println("metric=" + name + " error=true");
throw e;
}
};
}
// Predicate with logging
public <T> Predicate<T> loggedPredicate(String label, Predicate<T> predicate) {
return t -> {
boolean result = predicate.test(t);
System.out.println("predicate=" + label + " input=" + t + " result=" + result);
return result;
};
}
- Wrap stream pipeline stages in observability proxies for latency-critical operations.
- Use
Consumerfor side-effect logging in forEach — not for business logic. - Track predicate failure rates to identify broken validation rules.
- Instrument
Supplier.get()calls to measure lazy initialization costs. - Add metric tags for function names when using functional composition patterns.
Security Notes
- Lambda capture of mutable state: Lambdas that capture and mutate external state create data races in concurrent contexts. Keep captured variables effectively immutable.
- Deserializing lambdas: Serialized lambdas (used in distributed caches or session storage) can be a vector for code injection if the classloader is compromised. Avoid serializing lambdas from untrusted sources.
- Predicate injection: User-supplied predicates in search or filtering APIs must be sandboxed — a malicious predicate could cause denial-of-service via exponential complexity (ReDoS).
Pitfalls
Consumer.andThenexecutes left-to-right, not right-to-left:a.andThen(b)means “apply a, then apply b to the result of a” — this is the natural flow but easy to misread as the opposite.Function.composeorder:f.compose(g).apply(x)appliesfto the result ofg(x)— the composed function applies right-to-left.f.andThen(g)applies left-to-right.- Boxing in stream map:
stream.map(Integer::sum)wheresumisBinaryOperator<Integer>causes boxing. UsemapToIntandsum()for primitive optimizations. Predicate.isEqualusesObjects.equalsnot==: Two differentStringinstances with the same content are considered equal byPredicate.isEqual— this may or may not be the intended behavior.Supplieris evaluated once per.get()call: Each call toget()re-evaluates the supplier body — it is not memoized by default.
Quick Recap
Predicate<T>—test(T)returns boolean; use for filtering and conditions.Function<T,R>—apply(T)returns R; use for transformations.Supplier<T>—get()returns T; use for lazy initialization and factories.Consumer<T>—accept(T)returns void; use for side effects.BinaryOperator<T>extendsBiFunction<T,T,T>— both inputs and output are T.UnaryOperator<T>extendsFunction<T,T>— output is same type as input.- Use primitive specializations (
IntFunction,IntPredicate, etc.) to avoid boxing. andThenchains in execution order;composeapplies right-to-left.
Interview Questions
Model Answer: "`andThen` creates a pipeline that executes the calling function first, then passes its result to the provided function. `compose` executes the provided function first, then passes its result to the calling function — it is right-to-left composition. For example, `f.andThen(g)` means `g(f(x))`, while `f.compose(g)` means `f(g(x))`. Use `andThen` for sequential transformations where each step builds on the previous; use `compose` when you want to pre-process input before a main transformation."
Model Answer: "The generic functional interfaces like `Predicate
Model Answer: "`Consumer.andThen` chains two consumers to execute sequentially — the second consumer runs on the result of the first. If the first consumer throws, the second never runs. `Predicate.and` composes two predicates using logical AND — both are evaluated even if the first is false (short-circuit evaluation is not guaranteed for the combined predicate, though the JVM may optimize it). `Predicate.or` and `Predicate.negate` similarly compose boolean logic."
Model Answer: "`Predicate.isEqual(Object target)` returns a predicate that tests using `Objects.equals(o, target)` — it uses value equality (`equals()`), not reference equality (`==`). This means two different `String` instances containing the same characters will be considered equal. Use direct `==` comparison only when you need identity comparison. `isEqual` is useful when you want to match a specific object value in a collection without overriding `equals` on the matched-against object."
Model Answer: "No, the standard `java.util.function` interfaces do not declare checked exceptions. If your lambda body throws a checked exception, you have three options: catch and wrap in an unchecked exception (RuntimeException), use a custom functional interface that declares the exception, or use a wrapper utility that translates checked to unchecked. Libraries like Vavr provide alternative functional interfaces (`CheckedFunction1`, etc.) that preserve checked exception signatures."
Model Answer: "`BiFunction
Model Answer: "`Supplier.get()` is called fresh on every invocation — it is not memoized. Each call to `get()` executes the supplier body. To cache the result, use `Supplier
Model Answer: "`predicateA.or(predicateB)` returns a new `Predicate` that evaluates to `true` if either predicate returns `true`. The second predicate is not evaluated if the first returns `true` — this is short-circuit evaluation. This matters when the second predicate has side effects or is expensive. `Predicate.and()` similarly short-circuits by skipping the second predicate when the first returns `false`. `Predicate.negate()` has no short-circuit concern."
Model Answer: "These are specializations of `Function` for primitive return types — they accept any reference type and return a primitive without boxing. `ToIntFunction
Model Answer: "`a.andThen(b)` executes `a` first, then `b` on the result of `a` — the input to `b` is the same object that `a` received (since `Consumer` returns void). This is sequential chaining: both consumers see the same input. Using `a.accept(x); b.accept(x);` is equivalent but more verbose. `andThen` is the functional style for composing side effects. Note that if `a` throws, `b` never runs — `andThen` does not provide error recovery."
Model Answer: "`UnaryOperator
Model Answer: "`IntFunction
Model Answer: "`BinaryOperator.minBy(Comparator
Model Answer: "`ObjDoubleConsumer
Model Answer: "`Function.identity()` returns a function that always returns its input argument — `x -> x`. It is equivalent to `(x) -> x` but is the standard, reusable form. Use it when you need a function that passes through values unchanged, such as in collectors: `Collectors.mapping(Function.identity(), Collectors.toList())` to collect elements unchanged into a list. The lambda form `(x) -> x` is equally valid but `Function.identity()` is self-documenting."
Model Answer: "`biConsumerA.andThen(biConsumerB)` runs `A` then `B` sequentially on the same arguments. Calling `A.accept(a, b); B.accept(a, b);` separately is equivalent. Both consumers see the same `(a, b)` input — neither transforms the input for the other. `andThen` is the functional composition idiom for side-effect operations. If `A` throws, `B` is never called."
Model Answer: "`BiFunction` does not have its own `compose` or `andThen` methods in the same way `Function` does. However, `BiFunction.andThen(Function)` (available in Java 9+) allows composing a `BiFunction` with a `Function` to transform the result — `(T, U) -> R` followed by `R -> V` produces a `BiFunction
Model Answer: "`IntPredicate` takes a primitive `int` and returns `boolean` — no boxing of the input. `Predicate
Model Answer: "A `Supplier` for lazy initialization produces a value on demand — `Supplier
Model Answer: "These are to `BiFunction` what `ToIntFunction` is to `Function` — they accept two reference-type arguments (`T, U`) and return a primitive (`int`, `long`, `double`) without boxing the return value. They exist for the same performance reasons as their single-argument counterparts. Use them when you need a two-argument transformation that produces a primitive result."
Further Reading
- Oracle: java.util.function package documentation — official Javadoc for all functional interfaces
- Baeldung: Java Lambda Expressions and Functional Interfaces — lambda syntax and functional interface patterns
- Functional programming in Java 8 — Oracle’s overview of functional programming with
java.util.function - Stream API and lambda performance — benchmarking study on lambda overhead
- Vavr: Functional data structures — richer functional types complementing
java.util.function
Conclusion
java.util.function is the standardized vocabulary of functional programming in Java 8+. Before this package, every project had its own collection of single-method interfaces for callbacks, transformations, and predicates. The JDK’s built-in set covers the vast majority of use cases: Predicate for boolean tests, Function for transformations, Supplier for lazy production, Consumer for side effects, and their binary variants. Using these standardized types makes APIs self-documenting and interoperability between libraries seamless.
The functional interfaces are not just for streams — they appear throughout the JDK and any modern Java library. Spring uses Function<T, R> for bean transformers, Jackson uses Predicate for property filters, and most reactive libraries use Function for map operations. Once you internalize these types, you will recognize them everywhere in Java ecosystems and your code will become more idiomatic by using them consistently.
Function composition with andThen and compose is one of the most powerful patterns. andThen chains operations in execution order (apply this, then apply the other), while compose applies the other function first (right-to-left). For building transformation pipelines — parsing, validating, transforming, formatting — functional composition with andThen produces readable, testable, reusable code that is easy to modify.
The primitive specializations (IntPredicate, IntFunction, IntSupplier, etc.) exist for performance — they avoid boxing primitive values into wrapper objects. In hot paths like stream operations on large datasets, boxing overhead adds up. If you find yourself writing stream.map(Integer::sum) on an IntStream, switch to mapToInt and sum() to stay in primitives.
Functional interfaces integrate deeply with java.util.stream.Stream — every stream operation consumes one — and with java.util.Optional where map and flatMap accept Function and Function returning Optional respectively.
- Use standardized
java.util.functioninterfaces instead of custom single-method interfaces Predicatefor conditions,Functionfor transformations,Consumerfor side effects,Supplierfor lazy evaluationandThenchains in execution order;composeapplies right-to-left- Use primitive specializations (
IntFunction,IntPredicate) in hot paths to avoid boxing overhead - Functional interfaces cannot declare checked exceptions — wrap or use a custom variant for exception-heavy logic
- Method references (
String::length,User::getName) are the cleanest lambda form when they match the target signature
Category
Related Posts
Abstract Classes in Java
Learn about partially implemented classes that define contracts for subclasses using abstract methods and concrete implementations.
Arithmetic Operators in Java
Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.
Array Basics in Java
Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.