W3docs

Java Functional Interfaces

Single-abstract-method interfaces in Java that act as targets for lambdas, marked with @FunctionalInterface.

Java Functional Interfaces

A functional interface is an interface with exactly one abstract method. That one method is what a lambda or method reference compiles to. Runnable, Comparator<T>, Callable<V>, Supplier<T>, Function<T, R>, Predicate<T>, Consumer<T>, ActionListener, FileFilter — all functional interfaces. There are dozens already in the JDK and you'll write your own when none of them fit.

The previous chapter showed lambdas like () -> 42 and s -> s.length() "compiling to whatever interface the context needs." This chapter answers what makes an interface a valid target — the single-abstract-method (SAM) rule — and how @FunctionalInterface lets you say "yes, this is one, and I want the compiler to enforce it."

The SAM rule, precisely

To be functional, an interface must declare exactly one method that needs an implementation. The wording matters: not "exactly one method total," but "exactly one abstract method." Three categories of method don't count against the one:

  1. default methods — they already have a body, so an implementer doesn't need to provide one.
  2. static methods — they belong to the interface itself, not to implementers.
  3. public abstract methods that override a method on java.lang.Object — e.g. equals, hashCode, toString. Every class already inherits implementations from Object, so re-declaring them in an interface doesn't add a new requirement.

The third one surprises people. Comparator<T> declares boolean equals(Object), but it's still functional because that method comes from Object. The real abstract method is int compare(T, T).

@FunctionalInterface
interface MyComparator<T> {
  int compare(T a, T b);                          // the one SAM
  boolean equals(Object other);                   // Object override — doesn't count
  default MyComparator<T> reversed() {            // default — doesn't count
    return (a, b) -> compare(b, a);
  }
  static <T extends Comparable<T>> MyComparator<T> natural() {   // static — doesn't count
    return (a, b) -> a.compareTo(b);
  }
}

@FunctionalInterface — opt-in compile-time check

The annotation is optional. An interface is functional based on its shape, not on whether you annotate it. But annotating buys you two things:

  1. Compile error if the interface stops being functional. Add a second abstract method by accident and the compiler stops you immediately — at the interface, not at every call site that uses it as a lambda target.
  2. Documentation. The annotation signals "this is meant to be used as a lambda target," which is worth saying for any non-obvious case.
@FunctionalInterface
interface Validator<T> {
  boolean isValid(T value);
  boolean isInvalid(T value);     // <-- compile error: not a functional interface
}

Without the annotation that second method would silently turn Validator<T> into a non-functional interface, and the first lambda-style call site to use it would fail to compile with a confusing message far from the cause.

The annotation is also the convention for the JDK's own functional interfaces — Function, Predicate, Consumer, Supplier, Runnable, Callable all carry it.

Lambdas, method references, and anonymous classes are interchangeable

A functional interface accepts three kinds of value, and they're freely interchangeable:

Predicate<String> blank1 = s -> s.trim().isEmpty();               // lambda
Predicate<String> blank2 = String::isBlank;                        // method reference (since Java 11)
Predicate<String> blank3 = new Predicate<>() {                    // anonymous class
  @Override public boolean test(String s) { return s.trim().isEmpty(); }
};

All three implement the same Predicate<String> interface and produce equivalent values at the call site. The lambda and method reference are dramatically shorter; the anonymous class is reserved for the rare cases listed in the previous chapter (more than one method needed, method-local state, this referring to the new instance).

Generic functional interfaces

The interface can be parameterised — that's how a single declaration of Function<T, R> can be used for every transformation:

@FunctionalInterface
interface Mapper<T, R> {
  R map(T input);
}

Mapper<String, Integer> length = s -> s.length();
Mapper<Integer, String> hex    = n -> Integer.toHexString(n);

The parameters can be bounded, can include multiple type variables, and can be reused across interfaces — the standard library uses every variation.

Writing your own functional interface

Most of the time you should use the built-in interfaces in java.util.function — the next chapter walks them all. Write your own when:

  • The semantics deserve a name. Validator<T> reads better at a call site than Function<T, ValidationResult> even if the shape matches.
  • You need a checked exception. Function.apply throws nothing checked; if your operation throws IOException, write a SAM that declares it.
  • The shape isn't in the standard library. A method taking three arguments (a tri-function) has no built-in interface — write one when you need it.
@FunctionalInterface
interface IOFunction<T, R> {
  R apply(T input) throws IOException;
}

IOFunction<Path, String> readAll = Files::readString;       // declared exception — built-in Function can't

A surprisingly large amount of "should I write this?" comes down to either readability or exception propagation.

Default methods earn their keep

The one place you'll write your own functional interface and also add default methods is when you want callers to be able to compose instances:

@FunctionalInterface
interface Filter<T> {
  boolean keep(T value);

  default Filter<T> and(Filter<T> other) {
    return v -> keep(v) && other.keep(v);
  }
  default Filter<T> negate() {
    return v -> !keep(v);
  }
}

Filter<Integer> positive = n -> n > 0;
Filter<Integer> even     = n -> n % 2 == 0;
Filter<Integer> posOdd   = positive.and(even.negate());

That's exactly the recipe the JDK uses for Predicate.and / or / negate, Function.andThen / compose, and Comparator.thenComparing. The single abstract method is the behaviour; the default methods are the composition algebra that surrounds it.

A worked example: writing one, annotating it, composing it

The program below defines a Filter<T> functional interface with two default methods, demonstrates the SAM rule (an extra abstract method would not compile), and shows lambdas, method references, and an anonymous class all implementing the same SAM.

java— editable, runs on the server

What to take from the run:

  • notBlank1 (lambda), notBlank2 (method reference chain), and notBlank3 (anonymous class) all implement the same Filter<String> interface — interchangeably. The lambda is the shortest; the anonymous class is reserved for cases lambdas can't handle.
  • positive.and(even.negate()) composed three filters into one without any extra method declarations. The default methods and and negate on the interface are the composition algebra — that's why the JDK adds them to Predicate, Function, and Comparator too.
  • SafelyFunctional<T> declares both apply(T) and boolean equals(Object), and it still compiled with @FunctionalInterface. The equals override is inherited from Object, so it doesn't count against the single-abstract-method rule.
  • If you remove a default keyword in Filter (turning one default into a second abstract method), the @FunctionalInterface annotation forces an immediate compile error at the interface declaration — long before any lambda call site sees confusing inference failures.

What's next

You can recognise a functional interface, write one when the JDK doesn't have what you need, and let the compiler enforce its shape. Almost always, though, the right answer is "use what's already there." The next chapter, Java Built-in Functional Interfaces, tours java.util.functionFunction, Predicate, Consumer, Supplier, their bi-variants, and the primitive specialisations that exist to avoid boxing.

Practice

Practice

An interface declares three methods: one abstract method, one `default` method, and `boolean equals(Object)` re-declared from `Object`. Is it a valid `@FunctionalInterface`?