Sitemap
Suraj

Suraj

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

Functional Composition in Java

5 min readSep 6, 2026

--

Functional programming isn’t only about passing functions around. We can also combine small functions to create larger functions.

This is called functional composition.

Functional composition = combining functions to produce a new function.

Think of functions as building blocks:

f1 → f2 → f3 → Result

Instead of writing one large function, we create small reusable functions and compose them.

1. Composing Function Objects

Suppose we have a Product and want to produce a formatted price message.

There are two transformations:

Product
│
▼
BigDecimal
│
▼
String

We can represent each transformation independently.

Product → Price

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

Shape:

Product → BigDecimal

Price → Message

Function<BigDecimal, String> priceToMessage =
price -> String.format("Price: %.2f", price);

Shape:

BigDecimal → String

Now we have two small functions:

Product ──► productToPrice ──► BigDecimal
│
▼
priceToMessage
│
▼
String

Instead of executing them separately, we can compose them.

2. andThen()

Function provides the andThen() method:

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

Read this naturally:

Convert the product to price, and then convert the price to a message.

The flow is:

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

Mathematically:

f.andThen(g)
x → f(x) → g(f(x))

The result is a completely new function:

Function<Product, String>

which performs both transformations.

3. Using the Composed Function

Previously, we might have written:

findProduct(products, predicate)
.map(Product::getPrice)
.map(price -> String.format("Price: %.2f", price))
.ifPresentOrElse(
System.out::println,
() -> System.out.println("Product not available")
);

After composing the transformations:

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

we can simply use:

findProduct(products, predicate)
.map(productToMessage)
.ifPresentOrElse(
System.out::println,
() -> System.out.println("Product not available")
);

So:

Two transformations
↓
Compose them
↓
One reusable Function

This becomes especially useful when the same transformation pipeline is needed in multiple places.

4. compose() vs andThen()

Function also provides:

compose()

It performs the same idea, but the order is reversed.

With andThen():

f.andThen(g)

execution is:

f → g

So:

g(f(x))

With compose():

g.compose(f)

execution is also:

f → g

because the function passed to compose() executes first.

Therefore, these are equivalent:

productToPrice.andThen(priceToMessage)

and:

priceToMessage.compose(productToPrice)

Both produce:

Product → BigDecimal → String

Easy Memory Trick

f.andThen(g)
↓
f FIRST
g SECOND
f.compose(g)
↓
g FIRST
f SECOND

Think:

andThen() reads in execution order.

That usually makes it easier to understand.

5. Composing Predicate Objects

Functional composition isn’t limited to Function.

Predicate also provides methods for combining conditions.

Suppose we want to find a product that is:

FOOD + CHEAP

Instead of writing one large predicate, create two small ones.

Get Suraj’s stories in your inbox

Join Medium for free to get updates from this writer.

Is it food?

Predicate<Product> isFood =
product -> product.getCategory() == Category.FOOD;

Is it cheap?

Predicate<Product> isCheap =
product -> product.getPrice().compareTo(priceLimit) < 0;

Each predicate has one responsibility:

Product ──► isFood  ──► true / false

Product ──► isCheap ──► true / false

Now we can combine them.

6. Predicate.and()

To require both conditions:

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

Conceptually:

             ┌──► isFood ────► true?
Product ─────┤
└──► isCheap ───► true?
│
▼
BOTH true?
│
▼
true

We can then pass the combined predicate anywhere a Predicate<Product> is expected:

Optional<Product> result = findProduct(products, isFoodAndCheap);

The caller doesn’t need to know that the predicate was built from multiple smaller predicates.

It simply sees:

Predicate<Product>

7. Other Useful Predicate Combinations

Predicate provides three particularly useful composition methods:

and()
or()
negate()

AND

isFood.and(isCheap)

Means:

FOOD && CHEAP

Both conditions must be true.

OR

isFood.or(isCheap)

Means:

FOOD || CHEAP

At least one condition must be true.

NOT

isFood.negate()

Means:

!FOOD

It reverses the predicate result.

These can also be chained:

isFood
.and(isCheap)
.and(isAvailable);

Which reads naturally as:

FOOD
AND
CHEAP
AND
AVAILABLE

8. Why Not Just Write One Big Lambda?

We could write:

Predicate<Product> predicate =
product ->
product.getCategory() == Category.FOOD &&
product.getPrice().compareTo(priceLimit) < 0;

For two simple conditions, that’s perfectly reasonable.

Composition becomes more useful when conditions are:

  • reused
  • dynamically selected
  • independently tested
  • combined in different ways

For example:

isFood
isCheap
isAvailable
isInStock
isDiscounted

We can build different searches:

isFood AND isCheap

isFood AND isAvailable

isCheap AND isDiscounted

isAvailable AND NOT isDiscounted

without rewriting the individual rules.

9. Practical Example: Dynamic Search Filters

Imagine an e-commerce search screen:

Category:     FOOD
Max Price: €10
Available: YES
Discounted: ANY

Instead of writing a separate search method for every combination, we can build predicates dynamically.

Conceptually:

User Search Criteria
│
▼
┌───────────────────────┐
│ isFood │
│ AND │
│ isCheap │
│ AND │
│ isAvailable │
└───────────────────────┘
│
▼
Predicate<Product>
│
▼
Search / Filter Products

This is where functional composition becomes particularly powerful.

Small rules become building blocks for larger business rules.

10. Function Composition vs Predicate Composition

The easiest way to distinguish them:

Press enter or click to view image in full size

Think:

Function
↓
TRANSFORM → TRANSFORM → TRANSFORM

----------------------------------------------------------------------

Predicate
↓
CONDITION + CONDITION + CONDITION

The Bigger Picture

Functional composition encourages us to build programs from small pieces of behavior.

Small Function
+
Small Function
+
Small Function
│
▼
COMPOSE
│
▼
Larger Behavior

For transformations:

Product
↓
Price
↓
Formatted Price
↓
Message

For conditions:

Product
↓
Food?
+
Cheap?
+
Available?
↓
Combined Predicate

This is one of the central ideas behind functional programming:

Build complex behavior by composing small, focused, reusable functions.

Key Takeaways

  • Functional composition means combining functions to create new functions.
  • Function.andThen() executes the current function first, followed by the next function.
  • Function.compose() executes the supplied function first.
  • Predicate.and() combines conditions using logical AND.
  • Predicate.or() combines conditions using logical OR.
  • Predicate.negate() reverses a condition.
  • Small functions are easier to reuse, test, and combine.
  • Composition is especially useful for dynamic rules such as search filters and business criteria.

Remember This

Transformation?

Product → Price → String
↓
Function.andThen()

--------------------------------------------------------------

Multiple conditions?

Food AND Cheap AND Available
↓
Predicate.and()

In short: Don’t always build one large function. Build small pieces of behavior and compose them like building blocks.

Continue Learning Functional Interfaces🚀

← Previous: Practical Examples of Standard Functional Interfaces | Next →: Coming Soon

--

--

Written by Suraj

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