skip to content

What does @FunctionalInterface enforce, and when would you use it?

level: middleimportance: should knowfreq 62%

answer

  1. Exactly ONE abstract method = SAM = lambda target
  2. Compile error if 0 or >1 abstract methods
  3. default/static/private + Object methods (equals/hashCode/toString) don't count
  4. Optional — lambdas work without it; it guards intent
  5. Examples: Runnable, Comparator, Function, Predicate, Supplier

basics

~20 s

@FunctionalInterface marks an interface that has exactly one abstract method, so it can be used with lambdas. If you add a second abstract method, the code won't compile. It's optional — it just documents intent and guards the contract.

solid answer

~50 s

@FunctionalInterface is a java.lang annotation that declares an interface is a functional interface — one with exactly one abstract method (a SAM, single abstract method). Such interfaces are the targets of lambda expressions and method references. The annotation's job is a compile-time guard: if the interface does not have exactly one abstract method (zero, or more than one), compilation fails. Crucially, default methods, static methods, and any public methods that override java.lang.Object (like equals, hashCode, toString) do not count toward the one-abstract-method rule. The annotation is optional — any interface meeting the SAM rule can be used with lambdas without it — but adding it documents intent and prevents a future maintainer from accidentally breaking lambda compatibility by adding a second abstract method. The JDK uses it on Runnable, Comparator, Function, Predicate, Supplier, Consumer, and so on.

code

java · 11 lines
java
@FunctionalInterface
interface Transformer {
    String apply(String in);            // the single abstract method

    default Transformer then(Transformer next) {  // allowed: default method
        return in -> next.apply(this.apply(in));
    }
    // String describe(); // adding this 2nd abstract method => COMPILE ERROR
}

Transformer upper = s -> s.toUpperCase();  // lambda assignable to the SAM type

go deeper

for a junior

Knows it means 'one abstract method' and enables lambdas; can recognize a functional interface.

for a middle

Explains which methods are excluded (default/static/private/Object), that it's optional, and the compile-time guard it provides.

for a senior

Designs functional interfaces with default/static helper methods, understands SAM/lambda target typing, and uses the annotation as an API contract.

for a principal

Guides API design choosing between custom functional interfaces and java.util.function types, considers binary-compatibility implications of adding default vs abstract methods to published functional interfaces.

## Background: what a lambda needs A **lambda expression** in Java (e.g., `x -> x + 1`) is a compact way to write an instance of an interface. But Java has to know *which* method the lambda body implements. That is only unambiguous if the interface has **exactly one abstract method**. Such an interface is called a **functional interface** (or **SAM type** — Single Abstract Method). Lambdas and **method references** (e.g., `String::length`) can be assigned only to functional interface types. ## What @FunctionalInterface does `@FunctionalInterface` is a marker annotation in `java.lang`. Placed on an interface, it makes the **compiler verify** that the interface genuinely has exactly one abstract method. If you violate that — by declaring two abstract methods, or none — you get a **compile-time error**. It has **no runtime effect** of its own (though it is `RUNTIME`-retained, so reflection can see it). Think of it as `@Override` for functional interfaces: an optional safety net that turns a contract violation into a build failure. ## What counts as 'the one abstract method' The rule is exactly **one** *abstract* method, but several kinds of methods are **excluded** from the count: 1. **`default` methods** — they have a body, so they are not abstract. 2. **`static` methods** — not inherited as instance behavior. 3. **`private` methods** (since Java 9) — implementation helpers. 4. **Methods that override a public method of `java.lang.Object`** — `equals(Object)`, `hashCode()`, `toString()`. These are excluded because every implementation already inherits them from `Object`, so they don't need a lambda body. So this is still a valid functional interface: ```java @FunctionalInterface interface Comparator2<T> { int compare(T a, T b); // the single abstract method boolean equals(Object o); // excluded (Object method) default Comparator2<T> reversed() { return (a, b) -> compare(b, a); } // excluded (default) } ``` This exactly mirrors why the real `java.util.Comparator` is a functional interface despite declaring `equals`. ## It is optional You do **not** need `@FunctionalInterface` for an interface to be usable with lambdas — any interface that meets the SAM rule works. The annotation's value is: - **Documentation of intent:** readers know the interface is meant for lambdas. - **Future-proofing:** if someone later adds a second abstract method, the build breaks immediately instead of silently making every existing lambda call site fail to compile in confusing ways. ## JDK examples The `java.util.function` package is full of them: `Function<T,R>`, `Predicate<T>`, `Supplier<T>`, `Consumer<T>`, `BiFunction<T,U,R>`, plus older ones like `Runnable`, `Callable`, `Comparator`, and `Iterable`-adjacent types. Many add `default`/`static` helper methods (e.g., `Function.andThen`, `Predicate.and`) while keeping exactly one abstract method. ## Common pitfalls - Believing the annotation is **required** for lambda use (it isn't). - Thinking `default`/`static`/`Object` methods break the SAM rule (they don't). - Adding a second abstract method 'temporarily' and being surprised by the compile error — that's the annotation doing its job. ## Takeaway Use `@FunctionalInterface` on any interface you intend as a lambda target. It documents the contract and guards it at compile time, while default/static/private/Object methods remain free to add.

  • Why is java.util.Comparator a valid functional interface even though it declares more than one method?
    Only one method (compare) is abstract; equals(Object) is excluded as an Object method, and methods like reversed()/thenComparing() are default or static, so they don't count.
  • Is @FunctionalInterface required to use an interface with a lambda?
    No. Any interface with exactly one abstract method works with lambdas; the annotation only adds a compile-time guard and documents intent.

saying these in an interview costs you the question

  • Saying it is required to use lambdas
  • Thinking default or static methods count as the abstract method
  • Forgetting that Object methods (equals/hashCode/toString) are excluded from the count
  • Claiming it has a runtime behavioral effect on lambda dispatch

context