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
- Function, Predicate, Consumer, Supplier
- BiFunction, UnaryOperator, BinaryOperator
- Picking the right interface by inputs/outputs
- Composing Functions with andThen and compose
- Combining Predicates with and, or, negate
- Writing your own @FunctionalInterface
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
- ❌ Calling the wrong method. It's apply for Function, test for Predicate, accept for Consumer, get for Supplier. Fix: match the interface.
- ❌ Mixing up andThen and compose. Different order, different result. Fix: remember andThen = this-first.
- ❌ Two abstract methods under @FunctionalInterface. Won't compile. Fix: keep one abstract method; make others default.
- ❌ Using Function<T,Boolean> instead of Predicate<T>. Works, but loses and/or/negate. Fix: prefer Predicate.
- ❌ Boxing in hot loops. Function<Integer,Integer> boxes; for primitives use IntUnaryOperator etc. Fix: use the primitive specialisations.
- ❌ Expecting a Consumer to return a value. It returns void. Fix: use a Function if you need a result.
Pro Tips
- 💡 Build rules from small predicates. isActive.and(isAdult) is more readable and testable than one big boolean.
- 💡 Reach for primitive specialisations (IntPredicate, ToIntFunction, IntUnaryOperator) to avoid autoboxing on hot paths.
- 💡 Name your own interface when a domain term (PriceRule, Validator) communicates intent better than Function.
- 💡 Predicate.not(...) (Java 11) reads nicely: list.stream().filter(Predicate.not(String::isBlank)).
📋 Quick Reference
| Interface | Method (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.andThen | f.andThen(g) | g(f(x)) |
| Function.compose | f.compose(g) | f(g(x)) |
| Predicate.and/or/negate | p.and(q) | Combine conditions |
| @FunctionalInterface | one abstract method | Compile-time check |
📚 Resources / Keep Learning
- 📘 java.util.function API — every built-in functional interface.
- 📗 dev.java: Functional Interfaces — a guided tutorial.
- 📙 Oracle Tutorial: Lambda Expressions — how lambdas target these interfaces.
🎉 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
- Previous: Method References
- Next: Checkpoint: Modern Java — Combine records, sealed types, switch patterns and lambdas — then a timed quiz
- Quick reference: Java cheat sheet
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.