Functional Interfaces
The built-in functional interfaces in java.util.function — Function, Predicate, Consumer, Supplier, and how to compose them.
Last reviewed: January 2025
🔍 What Is It?
Core Functional Interfaces:
Function<T, R> — takes T, returns R
- →apply(T t): R
- →andThen(Function): compose after
- →compose(Function): compose before
Predicate<T> — takes T, returns boolean
- →test(T t): boolean
- →and(Predicate), or(Predicate), negate()
Consumer<T> — takes T, returns void
- →accept(T t)
- →andThen(Consumer)
Supplier<T> — takes nothing, returns T
- →get(): T
BiFunction<T, U, R> — takes T and U, returns R
BiPredicate<T, U> — takes T and U, returns boolean
BiConsumer<T, U> — takes T and U, returns void
Primitive Specializations (avoid boxing overhead):
- →IntFunction<R>, LongFunction<R>, DoubleFunction<R>
- →IntPredicate, LongPredicate, DoublePredicate
- →IntConsumer, IntSupplier, IntUnaryOperator
- →ToIntFunction<T>, ToLongFunction<T>
UnaryOperator<T> — Function<T, T> (same type in and out)
BinaryOperator<T> — BiFunction<T, T, T>
🎯 Why Do We Need It?
Functional interfaces are the types that lambdas implement. Without them, there would be no standard way to pass functions as arguments. The java.util.function package provides standard types that the entire Java ecosystem uses — Streams, Optional, CompletableFuture, and all major frameworks speak this language.
🏗️ What Happens in Real Projects?
Functional interfaces appear everywhere:
// Spring Data Specification (Predicate)
Specification<User> isActive = (root, query, cb) -> cb.isTrue(root.get("active"));
// Optional (Supplier, Consumer, Function)
Optional.ofNullable(value)
.map(String::toUpperCase) // Function
.filter(s -> s.length() > 3) // Predicate
.ifPresent(System.out::println); // Consumer
// CompletableFuture (Function, Consumer, Supplier)
CompletableFuture.supplyAsync(() -> fetchData()) // Supplier
.thenApply(data -> transform(data)) // Function
.thenAccept(result -> save(result)); // Consumer⚙️ How It Works
@FunctionalInterface annotation:
Optional but recommended. Causes a compile error if the interface has more than one abstract method. Documents intent.
Function Composition:
Functions can be composed into pipelines:
Function<String, String> trim = String::trim;
Function<String, String> upper = String::toUpperCase;
Function<String, Integer> length = String::length;
Function<String, Integer> pipeline = trim.andThen(upper).andThen(length);
int result = pipeline.apply(" hello "); // 5Predicate Composition:
Predicate<String> notEmpty = s -> !s.isEmpty();
Predicate<String> notTooLong = s -> s.length() <= 100;
Predicate<String> valid = notEmpty.and(notTooLong);💻 Example
Functional Interfaces in Enterprise Code — How to use and compose functional interfaces for clean, reusable business logic.
🤔 Common Confusions
❓ What is the difference between Function and UnaryOperator?
UnaryOperator<T> extends Function<T, T> — it is a Function where the input and output type are the same. Use UnaryOperator when you are transforming a value to the same type (String -> String, Integer -> Integer). Use Function when input and output types differ (String -> Integer). The distinction helps readability and enables some additional composition methods.
❓ Why are there primitive specializations like IntFunction and LongPredicate?
Generic types like Function<Integer, R> require boxing (int -> Integer) and unboxing, which has overhead. Primitive specializations like IntFunction<R> work directly with int primitives, avoiding boxing. For performance-critical code processing millions of numbers, use primitive specializations. For typical business code, the generic versions are fine.
🚨 Common Production Issues
Not using primitive specializations for numeric streams — using Stream<Integer> instead of IntStream boxes every integer. For large numeric datasets, use IntStream, LongStream, DoubleStream.
Stateful lambdas in parallel streams — if a lambda captures mutable state, parallel stream operations are not safe. Keep lambdas stateless.
✅ Best Practices
Use built-in functional interfaces (Function, Predicate, Consumer, Supplier) instead of creating custom ones.
Create custom functional interfaces only for domain-specific semantics that add clarity.
Annotate custom functional interfaces with @FunctionalInterface.
Use primitive specializations (IntFunction, etc.) for performance-critical numeric code.
Name complex functions and predicates for readability: Predicate<User> isEligibleForPromotion = ...
🏛️ Architect's Note
Functional interfaces are the bridge between OOP and FP in Java. In domain-driven design, functional interfaces can represent domain operations: Validator<Order>, PricingStrategy, DiscountRule. These are more flexible than class hierarchies — you can compose them, swap them at runtime, and test them in isolation. This is the Strategy pattern expressed functionally.
💬 Frequently Asked Questions
🎯 Key Takeaways
🔗 Related Topics
🎯 Practice this topic in interviews
Core Java Interview Prep
Practice interview questions that connect directly to this technology track.
↗ Where this topic fits
Core Java Technology Guide
Return to the Core Java learning hub.
Backend Engineer Career Path
A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.
Full Stack Java Developer Career Path
A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.
Sources
Sources / References
Use these Core Java references to verify the concept in official docs and continue into the most relevant SplashRide pages.
Official documentation
Related SplashRide pages
SplashRide Core Java learning path
Browse the full Core Java topic collection and learning order on SplashRide.
Core Java Interview Prep
Practice Core Java interview questions and scenario-based answers.
Backend Engineer Career Path
A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.
Full Stack Java Developer Career Path
A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.
Lambda Expressions
Lambda expressions — Java's functional programming syntax that eliminates boilerplate and powers the Streams API.
Streams API
The Streams API — declarative data processing pipelines that replaced verbose loops in modern Java. How to use, when to use parallel streams, and common mistakes.