Sitemap
Suraj

Suraj

Technical Lead | 14+ yrs specializing in Java, Distributed Systems & System Design.

Module Summary — Working with Functional Interfaces

4 min readSep 7, 2026

--

Functional interfaces are the bridge between Java’s object-oriented type system and functional programming with lambdas.

Let’s bring the key concepts from this module together.

1. What Is a Functional Interface?

A functional interface has exactly one abstract method.

@FunctionalInterface
interface ProductFilter {
boolean accept(Product product);
}

The key rule:

Functional Interface
│
▼
Exactly ONE abstract method
│
├── default methods ✅
├── static methods ✅
└── Object methods ✅

Methods matching public methods from java.lang.Object, such as equals(), don't count toward the single-abstract-method requirement.

2. @FunctionalInterface

The annotation:

@FunctionalInterface

is optional.

An interface with exactly one abstract method is functional even without it.

So why use it?

@FunctionalInterface
│
├── Documents intent
│
└── Enables compiler validation

If someone accidentally adds another abstract method, the compiler reports an error.

Think of it similarly to @Override: optional, but useful for expressing intent and catching mistakes.

3. Standard Functional Interfaces

Java provides reusable functional interfaces in:

java.util.function

Instead of memorizing them individually, think about their shape:

Inputs → Output

The Core Four

  Function<T,R>
T ─────────────► R
Transform


Consumer<T>
T ─────────────► void
Do something


Supplier<T>
() ────────────► T
Produce


Predicate<T>
T ─────────────► boolean
Test

A simple memory trick:

Function  → TRANSFORM
Consumer → DO
Supplier → PROVIDE
Predicate → TEST

4. Operators

Operators are specialized forms of Function where the input and output types are the same.

UnaryOperator<T>

T ───► T

Equivalent in shape to:

Function<T, T>

Example:

UnaryOperator<Integer> doubleIt = n -> n * 2;

BinaryOperator<T>

T ──┐
├──► T
T ──┘

Equivalent in shape to:

BiFunction<T, T, T>

Example:

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

5. The Bi Family

The prefix:

Bi

means two inputs.

Therefore:

Press enter or click to view image in full size

Notice there is no BiSupplier.

A Supplier has no inputs by definition:

() → T

6. Functional Interfaces in Real Java APIs

These interfaces appear throughout Java’s standard APIs.

For example:

products.stream()
.filter(product -> product.getPrice() < 10)
.map(Product::getName)
.forEach(System.out::println);

We can understand this entirely in terms of functional interfaces:

List<Product>
│
▼
filter()
Predicate<Product>
Product → boolean
│
▼
map()
Function<Product,String>
Product → String
│
▼
forEach()
Consumer<String>
String → void

This mental model becomes extremely useful when working with Streams.

7. Functional Composition

Small functions can be combined into larger functions.

Get Suraj’s stories in your inbox

Join Medium for free to get updates from this writer.

For example:

Function<Product, BigDecimal> productToPrice = Product::getPrice;

Function<BigDecimal, String> priceToMessage = price -> "Price: " + price;

We can combine them:

Function<Product, String> productToMessage =
productToPrice.andThen(priceToMessage);

Visualized:

Product
│
▼
productToPrice
│
▼
BigDecimal
│
▼
priceToMessage
│
▼
String

Useful Function composition methods include:

andThen()
compose()

8. Predicate Composition

Predicates can also be combined.

Suppose we have:

Predicate<Product> isFood = ...;
Predicate<Product> isCheap = ...;

We can create:

Predicate<Product> isFoodAndCheap = isFood.and(isCheap);

Java provides:

and()      → A && B

or() → A || B

negate() → !A

This is especially useful for building dynamic rules:

Food
AND
Cheap
AND
Available
AND
In Stock
│
▼
Combined Predicate

Functional composition encourages us to build small reusable behaviors and combine them when needed.

9. Specialized Functional Interfaces

Java generics don’t work directly with primitive types.

For example:

BinaryOperator<Integer>

works with wrapper objects:

(Integer, Integer) → Integer

If we’re actually processing primitive int values, this can involve boxing and unboxing:

int ↔ Integer

Java therefore provides specialized functional interfaces.

Instead of:

BinaryOperator<Integer>

we can use:

IntBinaryOperator

which works directly with:

(int, int) → int

10. Primitive Families

The main specialized interfaces target:

int
long
double

Examples:

Press enter or click to view image in full size

Don’t try to memorize every specialized interface.

Learn to decode the name:

LongToDoubleFunction
│ │
│ └── Output: double
│
└────────── Input: long


long → double

The Complete Mental Model

Everything in this module can be connected like this:

               FUNCTIONAL INTERFACE
│
▼
One Abstract Method
│
▼
Lambda / Method Reference
│
▼
Behavior as Data
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Function Predicate Consumer
T → R T → boolean T → void
│ │ │
└──────────────┼──────────────┘
│
▼
Functional APIs
│
┌──────────┴──────────┐
▼ ▼
Composition Streams

And when primitives are involved:

Generic Functional Interface
│
▼
Wrapper Types
│
boxing/unboxing
│
▼
Primitive Specialization
│
▼
Int / Long / Double interfaces

Quick Revision Cheat Sheet

Functional Interface
→ Exactly one abstract method

@FunctionalInterface
→ Optional + compiler validation

Function<T,R>
→ T → R
→ Transform


Consumer<T>
→ T → void
→ Perform


Supplier<T>
→ () → T
→ Produce


Predicate<T>
→ T → boolean
→ Test


UnaryOperator<T>
→ T → T


BiFunction<T,U,R>
→ (T,U) → R


BiConsumer<T,U>
→ (T,U) → void


BiPredicate<T,U>
→ (T,U) → boolean


BinaryOperator<T>
→ (T,T) → T


Functional Composition
→ Combine small functions


Primitive Specialization
→ Avoid unnecessary boxing/unboxing

Final Takeaway

Don’t approach java.util.function as a collection of interfaces to memorize.

Instead, ask:

How many inputs?
+
What is the output?
↓
Recognize the function shape
↓
Choose the interface

Once you understand the shape of behavior, Function, Predicate, Consumer, Supplier, operators, Bi... variants, and primitive specializations become much easier to recognize.

This foundation becomes even more valuable in the next topic: the Java Stream API, where functional interfaces, lambda expressions, and method references come together to build expressive data-processing pipelines.

--

--

Suraj
Suraj

Written by Suraj

Technical Lead | 14+ yrs specializing in Java, Distributed Systems & System Design.