The java.util.function
package, introduced in Java 8, is a treasure trove of built-in functional interfaces that power lambdas, streams, and functional programming in Java.
Whether you're processing collections, handling events, or transforming data, these interfaces make your code more concise, testable, and declarative.
This tutorial explores the most commonly used functional interfaces with code examples and real-world applications.
🔍 What Are Functional Interfaces?
A functional interface is an interface with exactly one abstract method, which makes it compatible with lambda expressions and method references.
Each interface in java.util.function
is annotated with @FunctionalInterface
, ensuring this rule is enforced.
✅ Why Use Built-in Functional Interfaces?
Benefit | Description |
---|---|
✔️ Conciseness | Reduces boilerplate logic (no need for full class definitions) |
🔧 Reusability | Compose logic using function chains |
🧩 Integration | Compatible with Stream , Optional , CompletableFuture , etc. |
🧠 Declarative Code | Encourages cleaner, functional programming idioms |
🧱 Core Functional Interfaces in java.util.function
1. Function<T, R>
Represents a function that takes an input T
and returns a result R
.
Function<String, Integer> length = str -> str.length();
System.out.println(length.apply("Hello")); // 5
- Compose:
f.compose(g)
executesg
first, thenf
. - AndThen:
f.andThen(g)
executesf
first, theng
.
2. Predicate<T>
Represents a boolean-valued function of one argument.
Predicate<String> isEmpty = s -> s.isEmpty();
System.out.println(isEmpty.test("")); // true
and
,or
,negate()
can combine multiple predicates.
Predicate<String> valid = s -> s.length() > 2;
Predicate<String> notBlank = valid.and(s -> !s.isBlank());
3. Consumer<T>
Represents an operation that takes a single input and returns no result.
Consumer<String> printer = s -> System.out.println("Name: " + s);
printer.accept("Alice");
- Use
andThen()
to compose multiple consumers.
4. Supplier<T>
Supplies a value without taking any input.
Supplier<Double> random = () -> Math.random();
System.out.println(random.get());
Useful for lazy evaluation or test data generation.
5. UnaryOperator<T>
A specialization of Function<T, T>
for same-type input and output.
UnaryOperator<String> upper = s -> s.toUpperCase();
System.out.println(upper.apply("hello")); // HELLO
6. BinaryOperator<T>
A specialization of BiFunction<T, T, T>
for same-type input and output.
BinaryOperator<Integer> sum = (a, b) -> a + b;
System.out.println(sum.apply(3, 5)); // 8
7. BiFunction<T, U, R>
Represents a function that takes two arguments and produces a result.
BiFunction<String, Integer, String> repeat = (s, n) -> s.repeat(n);
System.out.println(repeat.apply("A", 3)); // AAA
8. BiConsumer<T, U>
Accepts two inputs and returns nothing.
BiConsumer<String, Integer> agePrinter = (name, age) -> System.out.println(name + " is " + age);
agePrinter.accept("Bob", 30);
9. BooleanSupplier
, IntSupplier
, LongSupplier
, etc.
Primitive-specialized versions for performance optimization.
BooleanSupplier check = () -> true;
System.out.println(check.getAsBoolean());
🔁 Composing Functional Interfaces
Function<String, String> trim = String::trim;
Function<String, String> toUpper = String::toUpperCase;
Function<String, String> pipeline = trim.andThen(toUpper);
System.out.println(pipeline.apply(" hello ")); // HELLO
🧠 Using with Collections and Streams
List<String> names = List.of("Anna", "Bob", "Amanda");
List<String> filtered = names.stream()
.filter(name -> name.startsWith("A")) // Predicate
.map(String::toUpperCase) // Function
.collect(Collectors.toList());
🧵 Functional Interfaces in Multithreaded Code
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> System.out.println("Running task")); // Runnable is a functional interface
📌 What's New in Java?
Java 8
- Introduced
java.util.function
- Lambdas, Streams,
Optional
,CompletableFuture
Java 9
Optional.ifPresentOrElse()
Flow API
for reactive programming
Java 11+
var
support in lambdas- Stream API enhancements
Java 21
- Structured concurrency with lambdas
- Scoped values and virtual threads compatible with functional interfaces
✅ Conclusion and Key Takeaways
java.util.function
provides powerful abstractions for lambda-based programming.- Interfaces like
Function
,Predicate
,Consumer
, andSupplier
cover most use cases. - These interfaces integrate seamlessly with collections, streams, and async code.
- Mastering these interfaces makes your code modern, testable, and expressive.
❓ FAQ
Q1: Are all interfaces in java.util.function annotated with @FunctionalInterface?
Yes, to enforce single-abstract-method design.
Q2: Can I use these interfaces with streams?
Absolutely. They are the foundation of the Streams API.
Q3: What’s the difference between Function and UnaryOperator?UnaryOperator<T>
is a Function<T, T>
used when input and output types match.
Q4: Can functional interfaces throw exceptions?
Built-in ones don’t allow checked exceptions. You must wrap them manually.
Q5: Are these interfaces thread-safe?
No, thread-safety depends on implementation and usage context.
Q6: What’s the difference between Consumer and BiConsumer?Consumer<T>
takes one argument; BiConsumer<T,U>
takes two.
Q7: Should I always use built-in functional interfaces?
Prefer built-ins, but define custom ones if your use case doesn’t fit.
Q8: Can I combine multiple interfaces?
Yes, using andThen
, compose
, or
, negate
, etc.
Q9: Are primitive versions like IntSupplier faster?
Yes, they avoid boxing/unboxing overhead.
Q10: Do Spring or JavaFX use these interfaces?
Yes, especially in event listeners, reactive flows, and bean registration.