Functional Interfaces in Java — Single Abstract Method Contracts
Fast recall
Quick revision
Core answer
default, static, and private methods, plus methods inherited from Object (which don't count toward the SAM rule). The single abstract method makes the interface eligible as the target type for a lambda expression or method reference. The `@FunctionalInterface` annotation is optional but recommended on intentionally functional interfaces — it makes the compiler enforce the SAM constraint, so accidentally adding a second abstract method (now or later) becomes a compile error rather than a silent breaking change. Standard library examples: Runnable (void run()), Callable (V call() throws Exception), Comparator (int compare(T, T)), and the java.util.function family (Function, Consumer, Supplier, Predicate, BiFunction, etc.). Define a custom one when no standard interface fits — for example, when you need to declare a checked exception or use more than two parameters. Common rule: keep functional interfaces small and focused; if you find yourself adding methods, you've probably outgrown the pattern.Whether the candidate knows the SAM rule, the role of @FunctionalInterface as a compile-time check, and can identify standard functional interfaces and when to write a custom one
Thinking @FunctionalInterface is required for lambda compatibility (it's not). Confusing 'one abstract method' with 'one method' (default/static methods don't count). Defining custom interfaces when a standard one would do
Mention that Object methods (equals, hashCode, toString) don't count toward SAM, discuss the value of @FunctionalInterface for API stability, and know when to use checked-exception variants
Interview-ready guide
Interview answer
A functional interface is any interface with exactly one abstract method — the SAM rule. That's the only requirement. It can have any number of default, static, private (Java 9+), and Object-overriding methods, none of which count toward the SAM.
The SAM rule — what counts and what doesn't
Counts:
- Abstract methods declared by the interface
Doesn't count:
defaultmethods (have implementation)staticmethods (belong to the interface)privatemethods (helpers, Java 9+)- Overrides of
Objectmethods (equals,hashCode,toString)
This is why Comparator is a functional interface despite having many methods — only compare(T, T) is abstract.
`@FunctionalInterface` annotation
- Optional — the compiler treats SAM-compliant interfaces as functional whether you annotate or not
- Recommended for intentionally functional interfaces:
- Documents intent
- Compiler enforces the SAM rule — adding a second abstract method becomes a compile error
- Catches the mistake at the interface, not at every lambda use site
- Don't annotate interfaces that happen to be SAM but aren't meant to be functional (like
Closeable)
Standard library — use these first
Core four in java.util.function:
Function— transforms T to RPredicate— returns booleanConsumer— returns voidSupplier— no input, returns T
Two-argument variants: BiFunction, BiPredicate, BiConsumer
Same-type operators: UnaryOperator, BinaryOperator
Primitive specializations (avoid boxing): IntFunction, ToIntFunction, IntPredicate, etc.
Pre-existing functional interfaces: Runnable, Callable, Comparator — work seamlessly with lambdas.
Default methods enable composition
Function.andThen,Function.compose,Function.identity()Predicate.and,or,negate,Predicate.not(...)(Java 11+)Comparator.comparing,thenComparing,reversed- This is why functional interfaces with one abstract method can still expose a rich API
When to define a custom functional interface
1. Need a checked exception
@FunctionalInterface interface ThrowingFunction{ R apply(T t) throws Exception; } - Standard
Functiondoesn't declare exceptions — forces lambda authors to wrap
2. More than two parameters
- Standard library stops at
Bi*— define aTriFunctionif you need three
3. Domain-meaningful name
RetryPolicyreads better thanBiPredicate- Even if structurally equivalent, the name documents intent at every use site
4. Domain-specific default methods
- Add convenience methods like
serializeAll(collection)that build on the SAM
Pitfalls
- Confusing 'one method' with 'one abstract method' — defaults and statics don't count
- Boxing overhead —
Functionboxes; useIntUnaryOperatorin hot paths - Over-customizing — don't define
MyFunctionwhenFunctionworks - Overly clever interfaces — if a functional interface has 5+ default methods, consider a regular interface or abstract class
- Forgetting `@FunctionalInterface` on intentionally functional interfaces — a future maintainer might break the contract
Recommendation: Always start by checking if a standard java.util.function interface fits — 90% of the time it does. Add `@FunctionalInterface` to any interface you intentionally design for lambda use; it costs nothing and prevents accidental breakage. Define a custom interface only when you have a specific reason: a checked exception, more than two parameters, a domain-meaningful name, or domain-specific defaults. Use primitive specializations (IntFunction, ToIntFunction) in performance-sensitive paths to avoid boxing. Lean on default methods for composition — they're the secret ingredient that makes Java's functional interfaces ergonomic. The pattern is: keep the SAM small and abstract, layer convenience as defaults, use the annotation as a guardrail.
Functional Interfaces in Java — Single Abstract Method Contracts
Direct Answer
A functional interface is any interface with exactly one abstract method (SAM) — it can have any number of default, static, and private methods, plus methods inherited from Object (which don't count toward the SAM rule). The single abstract method makes the interface eligible as the target type for a lambda expression or method reference. The @FunctionalInterface annotation is optional but recommended on intentionally functional interfaces — it makes the compiler enforce the SAM constraint, so accidentally adding a second abstract method (now or later) becomes a compile error rather than a silent breaking change. Standard library examples: Runnable (void run()), Callable<V> (V call() throws Exception), Comparator<T> (int compare(T, T)), and the java.util.function family (Function<T,R>, Consumer<T>, Supplier<T>, Predicate<T>, BiFunction, etc.). Define a custom one when no standard interface fits — for example, when you need to declare a checked exception or use more than two parameters. Common rule: keep functional interfaces small and focused; if you find yourself adding methods, you've probably outgrown the pattern.
Common Mistakes & Pitfalls
Thinking @FunctionalInterface is required for lambda compatibility (it's not). Confusing 'one abstract method' with 'one method' (default/static methods don't count). Defining custom interfaces when a standard one would do
Check your understanding
Answer these without looking back at the article.
- Why don't `default` and `static` methods count toward the SAM?
- Is `@FunctionalInterface` required for an interface to be used with a lambda?
- Why does `Comparator<T>` qualify as a functional interface?
- When would you write a `ThrowingFunction` instead of using `Function`?
- What's the boxing penalty of `Function<Integer, Integer>` vs `IntUnaryOperator`?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.