Functional interfaces in Java
The single-abstract-method rule and @FunctionalInterface, the java.util.function families and primitive specialisations, composition, the four kinds of method reference, how lambdas compile through invokedynamic, and checked exceptions in lambdas.
On this page
🎯 What is a functional interface?
A functional interface is an interface with exactly one abstract method. That single method is the "shape" a lambda or method reference fills in. Predicate<Order> has one abstract method, boolean test(Order o), so o -> o.total() > 500 is a complete implementation of it, no class required.
Lambdas arrived in Java 8, and the trick that made them fit into a 20-year-old type system is the functional interface. Java didn't add a "function type" like (Order) -> boolean. It reused interfaces, and said: if an interface has one abstract method, a lambda can stand in for it. Interviewers like this topic because it sits right on the line between syntax and the JVM. Anyone can write x -> x * 2; fewer people can tell you which interface it became, what javac generated, or why forEach won't let them throw an IOException.
What you’ll learn
- What makes an interface functional, and why
default,staticandObjectmethods don't count towards the one abstract method - The
java.util.functionfamilies (Function,Supplier,Consumer,Predicate, operators) and when to reach forIntPredicateorToLongFunctionto avoid boxing - How to compose behaviour with
andThen,compose,Predicate.andandPredicate.not - The four kinds of method reference and the eager-evaluation trap with bound receivers
- How a lambda compiles: a private
lambda$method,invokedynamic,LambdaMetafactoryand a hidden class, with non-capturing lambdas reused as singletons - Practical options for checked exceptions inside lambdas, and how to design your own functional interface
For this lesson, we'll focus on the single-abstract-method rule, the java.util.function toolbox, composition, method references, how lambdas compile, checked exceptions, and how to design your own functional interface.
Below the line (out of scope)
- Callbacks and listeners built on these interfaces: Callback functions in Java
- Pure functions, immutability and streams as a style: Functional programming in Java
- Default and static interface methods in general: Static and default methods in interfaces
The 30-second answer
A functional interface has exactly one abstract method; default methods, static methods and re-declared Object methods like equals don't count. @FunctionalInterface is optional, but it makes the compiler enforce the rule. java.util.function gives you 43 ready-made shapes: Function, BiFunction, Supplier, Consumer, Predicate, UnaryOperator, BinaryOperator, plus primitive versions like IntPredicate that avoid boxing. A lambda compiles to a private method plus an invokedynamic call site; on first use LambdaMetafactory spins a hidden class, so there's no $1.class file, and a non-capturing lambda is created once and reused.
Interview radar
Asked very often: What makes an interface functional, what does @FunctionalInterface do, and can you name the core java.util.function types?
Senior rounds probe: How a lambda compiles to invokedynamic and LambdaMetafactory, why lambdas can't throw checked exceptions through Function, and when primitive specialisations matter.
Production angle: A hot path using Function<Integer, Integer> that boxes every value and shows up as allocation pressure in a profiler.
01Picture it first: one socket, many plugs
Think of a functional interface as a wall socket with one specific shape. The socket doesn't care what you plug in, a lamp, a charger, a kettle, as long as the plug matches. Predicate<Order> is a socket shaped "takes an Order, returns a boolean". A lambda, a method reference or a full class can all be plugs, provided their shape fits.
That also explains why the same lambda text can become different types. s -> s.isEmpty() fits Predicate<String> and also Function<String, Boolean>. The lambda has no type of its own; it takes the type of the socket it's plugged into, which the JLS calls the target type.
The standard library ships a whole wall of sockets, grouped by shape. Here's the map I keep in my head:
A map of the java.util.function families grouped by shape: Function and BiFunction transform, Supplier produces, Consumer accepts, Predicate tests, UnaryOperator and BinaryOperator keep the same type, each with primitive variants such as IntPredicate and ToLongFunction
Notice the naming pattern, because it lets you guess an interface you've never used. Bi means two inputs. A primitive prefix like Int means the input is primitive (IntPredicate.test(int)). To plus a primitive means the output is primitive (ToIntFunction.applyAsInt(T)). Obj plus a primitive mixes an object and a primitive (ObjIntConsumer.accept(T, int)).
02The one-abstract-method rule under the hood
Try it yourself first
Before reading on: which of these three interfaces are functional? Count abstract methods carefully.
@FunctionalInterface
interface PriceRule {
long apply(long cents); // abstract
default PriceRule then(PriceRule next) { // default: doesn't count
return c -> next.apply(apply(c));
}
static PriceRule identity() { return c -> c; } // static: doesn't count
boolean equals(Object o); // Object method: doesn't count
String toString(); // Object method: doesn't count
}
interface Auditable {
void audit();
void flush(); // two abstract methods
}
interface RefundRule extends PriceRule {} // inherits exactly one
PriceRule is functional: one abstract method, apply. Auditable is not, so Auditable a = () -> {} fails with "incompatible types: Auditable is not a functional interface". RefundRule is functional, because it inherits one abstract method and adds none.
Here's the count the compiler does for each of the three, member by member.
PriceRule's members counted one by one: apply is abstract and counts, while the default, static and re-declared Object methods don't, so the total is one and it is functional; Auditable has two abstract methods and is not functional; RefundRule inherits one and is functional
Notice three things:
defaultandstaticmethods have bodies, so they aren't abstract, and a lambda doesn't need to supply them. This is howFunctioncan haveandThenand still be a lambda target.- Re-declaring an
Objectmethod doesn't add an abstract method. Every implementation already inheritsequalsandtoStringfromObject. That's whyComparatordeclaresboolean equals(Object)and is still functional (JLS §9.8). @FunctionalInterfacechanges nothing at runtime. It's a compile-time check plus documentation. Without it,Runnable-style interfaces still work with lambdas; with it, the next person who adds a second abstract method gets a compile error instead of breaking every caller.
Generic methods are the one exception you might hit: an interface whose single abstract method is itself generic (<T> T convert(String s)) is functional, but a lambda can't implement it, because lambdas can't declare type parameters. Only a method reference can.
The families in practice
Try it yourself first
Before reading on, pick the right interface for each: "is this order eligible for free shipping?", "load the config lazily", "send this receipt email", "convert an Order to an OrderDto".
Predicate<Order> freeShipping = o -> o.totalCents() >= 50_000;
Supplier<Config> config = () -> Config.loadFrom("app.yaml");
Consumer<Receipt> sendEmail = r -> mailer.send(r.customerEmail(), r.render());
Function<Order, OrderDto> toDto = o -> new OrderDto(o.id(), o.totalCents());
BiFunction<Long, Integer, Long> discount = (cents, pct) -> cents * (100 - pct) / 100;
UnaryOperator<String> normalise = s -> s.trim().toLowerCase(); // T -> T
BinaryOperator<Long> add = Long::sum; // (T, T) -> T
ToLongFunction<Order> totalCents = Order::totalCents; // returns long, no Long box
IntPredicate isEven = n -> n % 2 == 0; // takes int, no Integer box
UnaryOperator<T> extends Function<T, T> and BinaryOperator<T> extends BiFunction<T, T, T>; they exist to make "same type in and out" readable. There's no TriFunction. If you need three inputs, either group them into a record or write your own interface.
Premium lesson
Keep reading with the Java Pass
The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
- Real world: checked exceptions and your own interfaces
- Advanced: overloads, this, and what interviewers poke at
- How to say it in the interview
From ₹199 · one-time payment · No auto-renewal
Further reading (10)
- JLS §9.8 Functional Interfaces
- JLS §15.27 Lambda Expressions
- java.util.function package summary (Java 21)
- FunctionalInterface annotation (Java 21)
- LambdaMetafactory (Java 21)
- Brian Goetz: Translation of Lambda Expressions
- JSR 335: Lambda Expressions for the Java Programming Language
- The Java Tutorials: Method References
- Effective Java, 3rd ed., Items 42 to 44: lambdas, method references, standard functional interfaces
- Baeldung: Functional Interfaces in Java