Built-in Functional Interfaces

Reviewed & published by Brayan K

A functional interface is an interface with exactly one abstract method, and Java's java.util.function package provides ready-made ones — Function, Predicate, Consumer, Supplier and more — that your lambdas and method references plug into.

Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

Before You Start

You should know lambdas and method references, plus basic generics like Function<T, R>. Available since Java 8.

What You'll Learn

The Big Idea — Standard Shapes of Behaviour

💡 Analogy: Think of these interfaces as standard plug shapes for behaviour. A Supplier is a vending machine — press it, get something out, no input needed. A Consumer is a shredder — feed something in, nothing comes back. A Predicate is a metal detector — pass something through, get a yes/no. A Function is a juicer — fruit in, juice out. Once you know the shape, any lambda of that shape just clicks in.

Because these shapes are standard, the whole Stream API and countless libraries speak the same language — you write a lambda and it fits everywhere the matching interface is expected.

1️⃣ The Core Four

Memorise these by their one method: Function<T,R>.apply (T → R), Predicate<T>.test (T → boolean), Consumer<T>.accept (T → void), and Supplier<T>.get (void → T).

2️⃣ BiFunction, UnaryOperator, BinaryOperator

BiFunction<T,U,R> takes two inputs. When a Function's input and output are the same type, the specialised UnaryOperator<T> (one arg) and BinaryOperator<T> (two args) read more clearly than spelling out Function<T,T>.

import java.util.function.BiFunction;
import java.util.function.UnaryOperator;
import java.util.function.BinaryOperator;

public class Main {
    public static void main(String[] args) {
        // BiFunction<T,U,R>: two inputs, one output.
        BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
        System.out.println("BiFunction : " + add.apply(3, 4));

        // UnaryOperator<T>: a Function whose input and output are the SAME type.
        UnaryOperator<String> exclaim = s -> s + "!";
        System.out.println("UnaryOp    : " + exclaim.apply("wow"));

        // BinaryOperator<T>: a BiFunction with both args and result the same type.
        BinaryOperator<Integer> max = (a, b) -> a >= b ? a : b;
        System.out.println("BinaryOp   : " + max.apply(9, 5));
    }
}

3️⃣ Composing & Combining

The real power is building bigger behaviour from small pieces. Function has andThen (this, then next) and compose (next, then this). Predicate has and, or, and negate. Each returns a brand-new function, so you can chain freely.

import java.util.function.Function;
import java.util.function.Predicate;

public class Main {
    public static void main(String[] args) {
        // Function composition: andThen runs left-to-right; compose runs right-to-left.
        Function<Integer, Integer> times2 = n -> n * 2;
        Function<Integer, Integer> plus3  = n -> n + 3;

        System.out.println("andThen: " + times2.andThen(plus3).apply(5)); // (5*2)+3 = 13
        System.out.println("compose: " + times2.compose(plus3).apply(5)); // (5+3)*2 = 16

        // Predicate combinators: and, or, negate.
        Predicate<Integer> positive = n -> n > 0;
        Predicate<Integer> even     = n -> n % 2 == 0;

        System.out.println("and    : " + positive.and(even).test(4));   // true
        System.out.println("or     : " + positive.or(even).test(-2));   // true (even)
        System.out.println("negate : " + positive.negate().test(-1));   // true
    }
}

4️⃣ Writing Your Own @FunctionalInterface

When none of the built-ins fits — for example you want a descriptive name — declare your own interface with a single abstract method and mark it @FunctionalInterface. The annotation makes the compiler enforce the one-method rule, while you can still add default and static helper methods.

public class Main {
    // @FunctionalInterface enforces EXACTLY one abstract method at compile time.
    @FunctionalInterface
    interface Transformer {
        String transform(String input);
        // default methods are allowed and don't break the rule
        default Transformer thenTrim() {
            return input -> this.transform(input).trim();
        }
    }

    static String run(Transformer t, String s) { return t.transform(s); }

    public static void main(String[] args) {
        // A lambda IS an instance of the functional interface.
        Transformer upper = s -> "  " + s.toUpperCase() + "  ";
        System.out.println("[" + run(upper, "hi") + "]");

        // Use the default method to compose behaviour.
        Transformer upperTrimmed = upper.thenTrim();
        System.out.println("[" + run(upperTrimmed, "hi") + "]");
    }
}

🧠 Quick Recall

Predict the result before opening each answer.

??? x = () -> "now";

Answer: Supplier<String>. It takes no input and returns a value, which is exactly get().

Function<Integer,Integer> f = n -> n + 1;
Function<Integer,Integer> g = n -> n * 10;
System.out.println(f.andThen(g).apply(2));

Answer: 30. andThen runs f first (2+1=3), then g (3*10=30).

@FunctionalInterface
interface Two { void a(); void b(); }

Answer: No. @FunctionalInterface requires exactly one abstract method, but this has two. error: Two is not a functional interface. Remove one or make it default.

🎯 Your Turn — A length-range Predicate

Combine two Predicate<String>s with .and() to accept only 3–8 character strings.

import java.util.function.Predicate;

public class Main {
    public static void main(String[] args) {
        // YOUR TURN: build a Predicate<String> that is true only when a string's
        // length is between 3 and 8 (inclusive). Combine two predicates with .and().
        //
        // Expected output:
        //   true
        //   false
        //   false

        // Predicate<String> minLen = ...
        // Predicate<String> maxLen = ...
        // Predicate<String> valid  = minLen.and(maxLen);
        // System.out.println(valid.test("hey"));
        // System.out.println(valid.test("ab"));
        // System.out.println(valid.test("toolongword"));
    }
}

🧩 Mini-Challenge — A text pipeline

Chain three steps with andThen, mixing method references and a lambda.

import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        // MINI-CHALLENGE: build a text pipeline with andThen:
        //   trim -> upper-case -> append "!"
        // Use method references for the first two steps and a lambda for "!".
        //
        // Expected output:
        //   HELLO!

        // Function<String,String> trim  = String::trim;
        // Function<String,String> upper = String::toUpperCase;
        // Function<String,String> bang  = s -> s + "!";
        // Function<String,String> pipe  = trim.andThen(upper).andThen(bang);
        // System.out.println(pipe.apply("  hello "));
    }
}

Common Errors

Pro Tips

📋 Quick Reference

InterfaceMethod (shape)Note
Function<T,R>R apply(T)T → R
Predicate<T>boolean test(T)T → boolean
Consumer<T>void accept(T)T → side effect
Supplier<T>T get()() → T
BiFunction<T,U,R>R apply(T,U)(T,U) → R
UnaryOperator<T>T apply(T)T → T
BinaryOperator<T>T apply(T,T)(T,T) → T
Function.andThenf.andThen(g)g(f(x))
Function.composef.compose(g)f(g(x))
Predicate.and/or/negatep.and(q)Combine conditions
@FunctionalInterfaceone abstract methodCompile-time check

📚 Resources / Keep Learning

🎉 Lesson Complete!

You now know the core functional interfaces and how to choose one by its inputs and outputs, how to compose Functions with andThen/compose, how to combine Predicates with and/or/negate, and how to write your own @FunctionalInterface.

Practice quiz

What is a functional interface?

  • An interface with many methods
  • A class with no fields
  • An interface with exactly one abstract method
  • An enum

Answer: An interface with exactly one abstract method. A functional interface has exactly one abstract method, so a lambda or method reference can implement it.

Which interface takes a T and returns an R?

  • Function<T,R>
  • Predicate<T>
  • Consumer<T>
  • Supplier<T>

Answer: Function<T,R>. Function<T,R> maps an input of type T to an output of type R via apply.

Which interface takes a T and returns a boolean?

  • Function<T,Boolean>
  • Supplier<Boolean>
  • Consumer<T>
  • Predicate<T>

Answer: Predicate<T>. Predicate<T> tests a value and returns boolean via test.

Which interface takes nothing and returns a value?

  • Consumer<T>
  • Supplier<T>
  • Runnable
  • Predicate<T>

Answer: Supplier<T>. Supplier<T> produces a value with get() and takes no input.

Which interface takes a value and returns nothing?

  • Consumer<T>
  • Supplier<T>
  • Function<T,Void>
  • Predicate<T>

Answer: Consumer<T>. Consumer<T> performs a side effect via accept and returns nothing.

What is UnaryOperator<T>?

  • A Predicate
  • A two-argument function
  • A Function whose input and output are the same type T
  • A Supplier

Answer: A Function whose input and output are the same type T. UnaryOperator<T> extends Function<T,T> — same input and output type.

What does Function's andThen do?

  • Runs the other function first
  • Runs this function, then feeds its result to the next
  • Negates the function
  • Combines two predicates

Answer: Runs this function, then feeds its result to the next. f.andThen(g) computes g(f(x)) — this first, then the next.

What does Function's compose do?

  • Runs this function first
  • Same as andThen
  • Returns a Predicate
  • Runs the argument function first, then this one

Answer: Runs the argument function first, then this one. f.compose(g) computes f(g(x)) — the argument runs first.

Which Predicate methods combine or invert tests?

  • plus, minus
  • and, or, negate
  • map, filter
  • get, set

Answer: and, or, negate. Predicate offers and(), or(), and negate() to build compound conditions.

What does @FunctionalInterface do?

  • Makes an interface faster
  • Generates getters
  • Compile-time check that the interface has exactly one abstract method
  • Marks it deprecated

Answer: Compile-time check that the interface has exactly one abstract method. The annotation makes the compiler verify the single-abstract-method rule; default/static methods are still allowed.

Continue this course

Frequently asked questions

What is a functional interface, and why does it matter?

A functional interface is any interface with exactly one abstract method (a 'SAM' type — Single Abstract Method). That single method is what a lambda or method reference implements. It matters because lambdas have no type of their own; they take on the type of the functional interface they're assigned to. So Function, Predicate, Consumer, and friends are simply the standard functional interfaces in java.util.function that give your lambdas a name and a contract.

How do I remember which interface to use?

Ask two questions: does it take an input, and does it return something? Supplier takes nothing and returns a value (get). Consumer takes a value and returns nothing (accept). Predicate takes a value and returns boolean (test). Function takes a value and returns another value (apply). For two inputs, use BiFunction (or BiConsumer/BiPredicate). When input and output share a type, use UnaryOperator (one arg) or BinaryOperator (two args), which are just specialised Functions.

What is the difference between andThen and compose on Function?

Both chain two functions, but in opposite order. f.andThen(g) applies f first and then g to f's result — it reads left to right, the way you'd describe steps. f.compose(g) applies g first and then f — matching the mathematical g then f notation. With f = times2 and g = plus3 on input 5: andThen gives (5*2)+3 = 13, while compose gives (5+3)*2 = 16. Pick whichever order reads most naturally for your pipeline; andThen is usually clearer.

How do I combine predicates?

Predicate has three default methods for building compound conditions. p.and(q) is true only when both are; p.or(q) is true when either is; and p.negate() flips the result. They return new Predicates, so you can chain them: isActive.and(isAdult).or(isVip). This lets you assemble complex business rules from small, named, testable pieces instead of writing one giant boolean expression.

What does @FunctionalInterface actually do?

It is an optional annotation that asks the compiler to verify the interface really has exactly one abstract method. If you accidentally add a second abstract method, compilation fails with a clear error, protecting users who rely on passing a lambda. It does not change behaviour or performance — an interface with one abstract method is a functional interface with or without the annotation. Note that default and static methods don't count against the single-abstract-method rule, so you can add as many of those as you like.

Related lessons