Master Core Java Programming From Scratch

Clear, interactive, and structured coding lessons designed for absolute beginners.

Functional Interfaces in Java

Learn functional interfaces, the @FunctionalInterface annotation, built-in interfaces from java.util.function, lambda expressions, method references, and functional-style programming in Java.

What is a Functional Interface?

A functional interface is an interface that contains exactly one abstract method.

Functional interfaces are the target types for lambda expressions and method references.

Java
interface Calculator {

    int calculate(int a, int b);

}
Important: A functional interface can also contain default and static methods. The important requirement is that it has only one abstract method.

@FunctionalInterface Annotation

The @FunctionalInterface annotation tells the compiler and other developers that an interface is intended to be a functional interface.

Java
@FunctionalInterface
interface Calculator {

    int calculate(int a, int b);

}

If another abstract method is added, the compiler reports an error because the interface no longer satisfies the functional interface contract.

Functional Interface with Lambda

Java
@FunctionalInterface
interface Calculator {

    int calculate(int a, int b);

}

Calculator addition =
    (a, b) -> a + b;

int result =
    addition.calculate(10, 20);

System.out.println(result);

The lambda expression provides the implementation of the interface's single abstract method.

Built-in Functional Interfaces

Java provides commonly used functional interfaces in the java.util.function package.

Interface Input Output Main Method
Predicate<T> T boolean test()
Function<T,R> T R apply()
Consumer<T> T void accept()
Supplier<T> None T get()
UnaryOperator<T> T T apply()
BinaryOperator<T> T, T T apply()

Predicate<T>

Predicate represents a condition that returns either true or false.

Java
import java.util.function.Predicate;

Predicate<Integer> isPositive =
    number -> number > 0;

System.out.println(
    isPositive.test(10)
);

System.out.println(
    isPositive.test(-5)
);

Function<T,R>

Function accepts one input and produces one output.

Java
import java.util.function.Function;

Function<String, Integer> length =
    text -> text.length();

int result =
    length.apply("CIIT Institute");

System.out.println(result);

Consumer<T>

Consumer accepts an input but does not return a value.

Java
import java.util.function.Consumer;

Consumer<String> printer =
    text -> System.out.println(text);

printer.accept(
    "Welcome to CIIT Institute"
);

Supplier<T>

Supplier produces a value without receiving an input.

Java
import java.util.function.Supplier;

Supplier<String> message =
    () -> "Hello Java";

System.out.println(
    message.get()
);

UnaryOperator<T>

UnaryOperator represents a function where the input and output are of the same type.

Java
import java.util.function.UnaryOperator;

UnaryOperator<Integer> square =
    number -> number * number;

System.out.println(
    square.apply(5)
);

BinaryOperator<T>

BinaryOperator accepts two values of the same type and returns a value of that same type.

Java
import java.util.function.BinaryOperator;

BinaryOperator<Integer> addition =
    (a, b) -> a + b;

System.out.println(
    addition.apply(10, 20)
);

BiFunction<T,U,R>

BiFunction accepts two inputs and produces one output.

Java
import java.util.function.BiFunction;

BiFunction<Integer, Integer, Integer> multiply =
    (a, b) -> a * b;

System.out.println(
    multiply.apply(5, 4)
);

BiPredicate<T,U>

BiPredicate accepts two inputs and returns a boolean result.

Java
import java.util.function.BiPredicate;

BiPredicate<Integer, Integer> greater =
    (a, b) -> a > b;

System.out.println(
    greater.test(20, 10)
);

BiConsumer<T,U>

BiConsumer accepts two inputs and does not return a value.

Java
import java.util.function.BiConsumer;

BiConsumer<String, Integer> display =
    (name, age) ->
        System.out.println(
            name + " : " + age
        );

display.accept(
    "Amit",
    22
);

Creating Your Own Functional Interface

Java
@FunctionalInterface
interface MessageFormatter {

    String format(String message);

}

MessageFormatter formatter =
    message ->
        "[INFO] " + message;

System.out.println(
    formatter.format(
        "Application started"
    )
);

Functional Interface with Default Method

A functional interface may contain default methods because default methods are not abstract methods.

Java
@FunctionalInterface
interface Greeting {

    void sayHello();

    default void welcome() {

        System.out.println(
            "Welcome!"
        );

    }

}

Functional Interface with Static Method

Java
@FunctionalInterface
interface Calculator {

    int calculate(int a, int b);

    static int square(int number) {

        return number * number;

    }

}

Static methods belong to the interface itself and do not count as abstract methods.

Object Methods and Functional Interfaces

Methods that correspond to public methods of Object do not count toward the single abstract method requirement.

Java
@FunctionalInterface
interface Task {

    void execute();

    String toString();

}

The interface still qualifies as functional because toString() corresponds to a method from Object.

Functional Interfaces and Method References

Method references can be assigned to functional interface variables when their signatures are compatible.

Java
Consumer<String> printer =
    System.out::println;

printer.accept(
    "Hello from Java"
);

Functional Interfaces with Collections

Java
List<String> names =
    List.of(
        "Amit",
        "Neha",
        "Rahul"
    );

names.forEach(
    name ->
        System.out.println(name)
);

The forEach() operation accepts a Consumer.

Functional Interfaces and Streams

Stream operations frequently accept functional interfaces.

Java
List<Integer> numbers =
    List.of(
        10,
        15,
        20,
        25,
        30
    );

numbers.stream()
    .filter(
        number -> number > 20
    )
    .map(
        number -> number * 2
    )
    .forEach(
        System.out::println
    );
Key idea: filter() uses a predicate-like condition, map() transforms values, and forEach() consumes values.

Composing Functional Operations

Some functional interfaces provide methods that allow operations to be combined.

Java
Predicate<Integer> positive =
    number -> number > 0;

Predicate<Integer> even =
    number -> number % 2 == 0;

Predicate<Integer> positiveAndEven =
    positive.and(even);

System.out.println(
    positiveAndEven.test(10)
);

Function Composition

Function interfaces support operations such as andThen() and compose().

Java
Function<Integer, Integer> multiply =
    number -> number * 2;

Function<Integer, Integer> add =
    number -> number + 10;

Function<Integer, Integer> combined =
    multiply.andThen(add);

System.out.println(
    combined.apply(5)
);

Primitive Functional Interfaces

Java also provides primitive-specialized functional interfaces to reduce unnecessary boxing and unboxing in suitable cases.

Interface Purpose
IntPredicate Predicate for int values.
IntFunction<R> Accepts int and returns a reference type.
IntConsumer Consumes an int value.
IntSupplier Supplies an int value.
IntUnaryOperator Transforms one int into another int.
IntBinaryOperator Combines two int values.

Example

Functional interfaces are useful when business logic needs to be supplied dynamically.

Java
static int calculate(
    int a,
    int b,
    BinaryOperator<Integer> operation
) {

    return operation.apply(a, b);

}

int addition =
    calculate(
        10,
        20,
        (a, b) -> a + b
    );

int multiplication =
    calculate(
        10,
        20,
        (a, b) -> a * b
    );

System.out.println(addition);
System.out.println(multiplication);

Benefits of Functional Interfaces

  • Enables lambda expressions.
  • Reduces boilerplate code.
  • Supports functional-style programming.
  • Works naturally with the Stream API.
  • Makes behavior easy to pass as an argument.
  • Encourages reusable and composable operations.

Best Practices

  • Use @FunctionalInterface when defining an interface intended to be functional.
  • Prefer standard interfaces from java.util.function when they fit the use case.
  • Create custom functional interfaces when domain-specific meaning improves readability.
  • Keep lambda expressions short and readable.
  • Prefer method references when they make the code clearer.

Interview Questions

A functional interface is an interface with exactly one abstract method.

@FunctionalInterface is an annotation that documents and compiler-checks the intent that an interface should contain one abstract method.

Yes. A functional interface can have multiple default and static methods as long as it has only one abstract method.

Predicate<T> represents a function that accepts a value and returns a boolean result.

Function accepts an input and returns an output, while Consumer accepts an input and returns nothing.

Predicate returns a boolean result, while Function transforms an input value into an output value.

A lambda expression needs a target functional interface type, either explicitly or through type inference.
Summary

Functional interfaces contain one abstract method and form the foundation for lambda expressions and method references. Java provides Predicate, Function, Consumer, Supplier, UnaryOperator, BinaryOperator, BiFunction, BiPredicate, BiConsumer, and primitive-specialized interfaces for common functional programming tasks.