By WebNest Studio Engineering Team
Java - Core Tutorial
Functional Interfaces
A functional interface is an interface that declares exactly one abstract method, making it a valid target type for a lambda expression or method reference. Java 8 added the @FunctionalInterface annotation and a full set of general-purpose functional interfaces in the java.util.function package so that developers rarely need to declare their own.
Why it matters
Every lambda has a type, and that type is a functional interface. The four you will meet constantly are Predicate, Function, Consumer and Supplier: they are the parameter types of the Stream API and of much of modern Java. Recognising them tells you at once what kind of lambda a method expects.
The Single Abstract Method Rule
A functional interface can have any number of default methods and static methods, but only one abstract method — that single method is what the lambda's parameter list and body actually implement. The @FunctionalInterface annotation is optional but strongly recommended: it tells the compiler to raise an error if a second abstract method is accidentally added, catching the mistake at compile time instead of at the point of use.
Core Built-in Functional Interfaces
Rather than defining custom interfaces for common shapes of behavior, Java 8 ships a standard set in java.util.function:
Predicate<T>—boolean test(T t), used for conditions/filters.Function<T, R>—R apply(T t), transforms a value of type T into type R.Supplier<T>—T get(), takes no arguments and produces a value.Consumer<T>—void accept(T t), takes a value and performs an action, returning nothing.BiFunction<T, U, R>—R apply(T t, U u), like Function but with two input arguments.
Writing a Custom Functional Interface
When none of the built-in interfaces fit — for example, you want meaningful parameter names in the API or a domain-specific method name — you can declare your own single-abstract-method interface and annotate it with @FunctionalInterface for safety and clarity.
Example
Built-in functional interfaces and a custom one
import java.util.function.Predicate;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.Consumer;
import java.util.function.BiFunction;
public class FunctionalInterfaceDemo {
@FunctionalInterface
interface Greeter {
String greet(String name);
}
public static void main(String[] args) {
Predicate<Integer> isEven = n -> n % 2 == 0;
Function<String, Integer> length = String::length;
Supplier<String> greeting = () -> "Hello from Supplier";
Consumer<String> printer = s -> System.out.println("Consumed: " + s);
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
System.out.println(isEven.test(4));
System.out.println(length.apply("Webnest"));
System.out.println(greeting.get());
printer.accept("data");
System.out.println(add.apply(2, 3));
Greeter greeter = name -> "Welcome, " + name + "!";
System.out.println(greeter.greet("Asha"));
}
}
true
7
Hello from Supplier
Consumed: data
5
Welcome, Asha!
Common Mistakes
- Adding a second abstract method to an interface annotated with @FunctionalInterface — the compiler flags this as an error, which is exactly the point of the annotation.
- Confusing Supplier (no input, returns a value) with Consumer (takes input, returns nothing) — their signatures are opposites.
- Forgetting that default and static methods do not count toward the "single abstract method" rule, and assuming an interface with several default methods cannot be a functional interface.
Exercise
Define a Predicate that tests whether a number is even and a Function that squares a number. Print the result of testing 4, of squaring 5, and of testing 4 with the predicate negated.
Expected output: true, 25, false (one per line)
import java.util.function.Function;
import java.util.function.Predicate;
public class BuiltInInterfaces {
public static void main(String[] args) {
// TODO: Predicate<Integer> isEven
// TODO: Function<Integer, Integer> square
// TODO: print isEven for 4, square of 5, and the negated predicate for 4
}
}
Show hints
- A
Predicateis called withtest(value)and aFunctionwithapply(value). isEven.negate()returns a new predicate that gives the opposite answer.
Show solution
import java.util.function.Function;
import java.util.function.Predicate;
public class BuiltInInterfaces {
public static void main(String[] args) {
Predicate<Integer> isEven = n -> n % 2 == 0;
Function<Integer, Integer> square = n -> n * n;
System.out.println(isEven.test(4)); // true
System.out.println(square.apply(5)); // 25
System.out.println(isEven.negate().test(4)); // false
}
}
Quick quiz
1. Which interface takes no argument and returns a value?
- A. Consumer
- B. Predicate
- C. Supplier
- D. Function
Show answer
C. A Supplier has one method, get(), that takes nothing and returns a result.
2. Can a functional interface have default methods?
- A. No
- B. Only if they are private
- C. Only one
- D. Yes, any number, as long as it has exactly one abstract method
Show answer
D. Only abstract methods count. Predicate, for example, has default methods and(), or() and negate().
3. What does the @FunctionalInterface annotation do?
- A. Makes the compiler report an error if the interface does not have exactly one abstract method
- B. Makes the interface functional
- C. Makes the interface run faster
- D. It is required for lambdas to work
Show answer
A. It is optional. It documents the intent and guards against someone adding a second abstract method.
Interview questions
What is a functional interface?
Show answer
An interface with exactly one abstract method. It can also have default and static methods. Because there is only one method to implement, a lambda or method reference can be used wherever the interface is expected. Runnable, Comparator and Callable are examples that existed before Java 8.
Explain Predicate, Function, Consumer and Supplier.
Show answer
Predicate<T> takes a value and returns a boolean; it is used for filtering. Function<T, R> takes a value and returns another value; it is used for transforming. Consumer<T> takes a value and returns nothing; it is used for actions such as printing. Supplier<T> takes nothing and returns a value; it is used to produce or defer a value.
Key Points to Remember
- A functional interface has exactly one abstract method; it may have any number of default/static methods.
- @FunctionalInterface is optional but enables compile-time checking of the single-method rule.
- java.util.function provides Predicate, Function, Supplier, Consumer, and BiFunction for common cases.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering