A Java functional interface is an interface whose abstract methods amount to one logical method contract under the language rules. Lambdas and method references use that contract as their target type; the optional @FunctionalInterface annotation asks the compiler to verify that the interface qualifies. For common shapes, Java provides reusable types such as Function, Predicate, Consumer, and Supplier.
What makes an interface functional in Java?
The Java Language Specification (JLS) defines a functional interface by its abstract method contract, not by a simple count of declarations written in one source file. An interface qualifies when it has one functional abstract method, after accounting for inherited declarations and public instance methods matching methods of Object. See the Java SE 14 JLS, §9.8.
Inherited abstract methods can represent the same logical contract when their signatures are override-equivalent and their return types meet the specification’s compatibility rules. Consequently, several inherited declarations do not necessarily mean several distinct functional methods. Public Object methods such as toString do not add another contract, and default methods have implementations rather than adding abstract methods.
The JLS edition matters for release-specific rules. The cited Java SE 14 specification excludes sealed interfaces from being functional interfaces; check the JLS for the Java release you target before applying that detail to another release.
How lambdas and method references use functional interfaces
A lambda expression or method reference is not an untyped, standalone function value in Java. It is interpreted against a target type: a functional interface whose method parameters and result are compatible with the expression. The JDK documentation describes target typing in assignments, method invocations, and casts; see the Java SE 26 java.util.function package documentation.
@FunctionalInterface
interface Greeting {
String greet(String name);
}
Greeting greeting = name -> "Hello, " + name;
System.out.println(greeting.greet("Mina"));
Here, Greeting supplies the target contract: it accepts a String and returns a String. A method reference can target an existing compatible method too:
Rank #2
Predicate<String> isEmpty = String::isEmpty;
Likewise, an API that expects a predicate gives a lambda its target type at the call site, as in stream.filter(e -> e.getSize() > 10).
What @FunctionalInterface does—and does not do
@FunctionalInterface is an optional annotation expressing design intent. The compiler reports a diagnostic if an annotated type does not meet the functional-interface requirements. But an interface that satisfies those requirements remains a valid lambda target even if the annotation is absent. Oracle’s Java SE 26 FunctionalInterface API documentation describes the annotation as informative and notes that instances can be created with lambda expressions, method references, or constructor references.
Free tools Windows power users keep installed
One-click scans. No signup required.
For a custom interface intended as a lambda target, adding the annotation is useful: it catches changes that accidentally disrupt the single logical method contract. It does not create that contract or make an otherwise ineligible interface functional.
Which built-in functional interface should you choose?
Start with the behavior’s shape: how many inputs it takes, and whether it returns a value, a boolean, or nothing. The java.util.function package supplies general-purpose types for common patterns.
Rank #4
| Type | Shape | Typical role |
|---|---|---|
Function<T,R> |
T -> R |
Transform an input into a result |
Consumer<T> |
T -> void |
Perform an action using an input |
Predicate<T> |
T -> boolean |
Test an input, such as a filter condition |
Supplier<R> |
() -> R |
Produce a value without an input |
BiFunction<T,U,R> |
(T,U) -> R |
Combine two inputs into a result |
UnaryOperator<T> |
T -> T |
Transform a value while retaining its type |
BinaryOperator<T> |
(T,T) -> T |
Combine two values of the same type |
Names with an arity prefix, such as BiFunction, indicate additional inputs. The package also offers primitive-specialized variants for common shapes, which can avoid representing primitive values through boxed types. Consult the package documentation for the available interfaces and their contracts.
When a custom functional interface is a better fit
A standard type is a good choice when its generic meaning is clear. A custom interface is preferable when a domain-specific name makes an API easier to understand, when callers need domain-specific documentation, or when the contract does not match a supplied shape. The JDK package documentation expressly leaves room for useful shapes it does not cover and for purpose-specific interfaces owned by the APIs that use them.
Best Value
- Meaning: Is this simply a transformation, test, action, or value source—or does the behavior represent a named business concept?
- Inputs and result: Does a built-in type express the number of inputs and the return behavior accurately?
- Type specialization: Would a primitive-specialized interface communicate the API more directly and avoid boxing?
- API ownership: Does the library or package consuming the behavior already define an appropriate purpose-specific type?
Standard examples such as Runnable and Comparator also illustrate that functional interfaces are not limited to the java.util.function package. Choose a custom or existing domain interface for the clarity of its contract, not merely because it happens to have one method declaration.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

