Function Composition
Function composition is the process of connecting two or more functions so that the output produced by one function becomes the input of another. Instead of writing one large operation, you can build a sequence of small transformations and execute them as a single Function.
This is one of the most useful ideas in functional programming because real applications often process data through several steps. For example, an application may receive a username, remove unnecessary spaces, convert it to lowercase, and finally format it for storage. Each step can be represented by a separate Function and then composed into one transformation.
Why Function Composition Matters
Without composition, related transformations are often placed into one large lambda. That can work, but it becomes harder to reuse and test individual operations.
Function<String, String> process =
text -> text.trim().toLowerCase();
The same logic can be divided into smaller reusable functions.
Function<String, String> trim =
String::trim;
Function<String, String> lowerCase =
String::toLowerCase;
These functions can then be connected to form a processing pipeline. Each function has one clear responsibility.
Remember: Function composition means combining functions so that one function's result flows into another function's input.
Basic Function Composition
Java's Function<T, R> interface provides two important default methods for composition: andThen() and compose().
Function<Integer, Integer> doubleValue =
number -> number * 2;
Function<Integer, Integer> addFive =
number -> number + 5;
Function<Integer, Integer> combined =
doubleValue.andThen(addFive);
System.out.println(combined.apply(10));
The input is 10. First, doubleValue changes it to 20. Then addFive changes 20 to 25. Therefore, the final result is 25.
Using andThen()
The andThen() method executes the current Function first and then executes the supplied Function.
Function<Integer, Integer> multiply =
number -> number * 3;
Function<Integer, Integer> add =
number -> number + 2;
Function<Integer, Integer> result =
multiply.andThen(add);
System.out.println(result.apply(4));
The execution order is:
4 ↓ multiply: 4 × 3 = 12 ↓ add: 12 + 2 = 14 ↓ Result: 14
The most important point is that andThen() follows the natural left-to-right reading order: first this Function, then the next Function.
Using compose()
The compose() method works in the opposite order. The Function supplied to compose() executes first, followed by the current Function.
Function<Integer, Integer> multiply =
number -> number * 3;
Function<Integer, Integer> add =
number -> number + 2;
Function<Integer, Integer> result =
multiply.compose(add);
System.out.println(result.apply(4));
The execution order is:
4 ↓ add: 4 + 2 = 6 ↓ multiply: 6 × 3 = 18 ↓ Result: 18
This is the key difference between andThen() and compose().
| Method | First Operation | Second Operation |
|---|---|---|
| andThen() | Current Function | Supplied Function |
| compose() | Supplied Function | Current Function |
Understanding the Difference with a Simple Example
Suppose one Function adds 10 and another multiplies by 2. The order matters because these operations do not produce the same result when reversed.
Function<Integer, Integer> addTen =
number -> number + 10;
Function<Integer, Integer> multiplyByTwo =
number -> number * 2;
Function<Integer, Integer> first =
addTen.andThen(multiplyByTwo);
Function<Integer, Integer> second =
addTen.compose(multiplyByTwo);
System.out.println(first.apply(5));
System.out.println(second.apply(5));
The first Function performs 5 + 10 and then multiplies the result by 2, producing 30. The second first multiplies 5 by 2 and then adds 10, producing 20.
A quick memory trick: andThen() means "do this, and then that." With compose(), the supplied Function gets the first opportunity to process the value.
Composing String Functions
Function composition becomes particularly readable when working with text transformations.
Function<String, String> trim =
String::trim;
Function<String, String> lower =
String::toLowerCase;
Function<String, String> process =
trim.andThen(lower);
System.out.println(
process.apply(" JAVA ")
);
The input first passes through trim, producing "JAVA". That result then passes through lower, producing "java".
Composing Functions with Different Types
The Functions being composed do not need to use the same type throughout the entire pipeline. The output type of one Function must simply be compatible with the input type of the next Function.
Function<String, Integer> length =
String::length;
Function<Integer, String> message =
number -> "Length: " + number;
Function<String, String> result =
length.andThen(message);
System.out.println(
result.apply("Programming")
);
The data flows through three types: String → Integer → String. This illustrates why the generic input and output types of Function are so useful.
Multiple Function Composition
You are not limited to composing only two Functions. Multiple transformations can be connected to create a longer pipeline.
Function<Integer, Integer> addTen =
number -> number + 10;
Function<Integer, Integer> multiplyTwo =
number -> number * 2;
Function<Integer, Integer> subtractFive =
number -> number - 5;
Function<Integer, Integer> process =
addTen
.andThen(multiplyTwo)
.andThen(subtractFive);
System.out.println(process.apply(10));
The input travels through the Functions in sequence: 10 becomes 20, then 40, and finally 35.
Function Composition with Validation
In practical applications, transformations may need to happen in a specific order. For example, raw user input may first need cleaning before it can be converted into a number.
Function<String, String> clean =
String::trim;
Function<String, Integer> convert =
Integer::parseInt;
Function<String, Integer> process =
clean.andThen(convert);
System.out.println(
process.apply(" 250 ")
);
The input is first cleaned and then converted. If the order were reversed, the conversion would receive the untrimmed input and the behavior could become dependent on how that conversion handles the input.
Function Composition and Reusability
A major benefit of composition is that small Functions can be reused in different pipelines.
Function<String, String> trim =
String::trim;
Function<String, String> lower =
String::toLowerCase;
Function<String, String> upper =
String::toUpperCase;
Function<String, String> normalized =
trim.andThen(lower);
Function<String, String> display =
trim.andThen(upper);
The same trim Function is reused in two different transformations. This reduces duplication while keeping each operation focused.
Function.identity()
Java also provides the static method Function.identity(). It returns a Function that simply returns its input without modifying it.
Function<String, String> identity =
Function.identity();
System.out.println(
identity.apply("Java")
);
Identity functions are useful when a pipeline needs a function that preserves its input or when an API expects a Function even though no transformation is required.
Composition with Method References
Function composition works naturally with method references, allowing pipelines to remain compact and readable.
Function<String, String> trim =
String::trim;
Function<String, String> upper =
String::toUpperCase;
Function<String, String> process =
trim.andThen(upper);
System.out.println(
process.apply(" hello java ")
);
The method references provide the individual transformations, while andThen() defines their execution order.
Function Composition in Real Applications
In enterprise applications, the same idea can appear in many places. Input data may be cleaned, converted, validated, enriched, and finally formatted before it reaches another layer.
Function<String, String> clean =
String::trim;
Function<String, String> normalize =
String::toLowerCase;
Function<String, String> addPrefix =
value -> "USER-" + value;
Function<String, String> pipeline =
clean
.andThen(normalize)
.andThen(addPrefix);
System.out.println(
pipeline.apply(" Ravi ")
);
The resulting pipeline transforms the input in three focused stages instead of hiding everything inside one large block of code.
Common Beginner Mistakes
- Confusing the execution order of andThen() and compose().
- Trying to compose Functions whose input and output types are incompatible.
- Creating extremely long Function chains that become harder to understand than ordinary code.
- Using composition when a simple single Function would be clearer.
- Forgetting that the order of transformations can change the final result.
Best Practices
- Keep each Function small and focused on one transformation.
- Use andThen() when transformations should execute in the same order they are written.
- Use compose() when the supplied Function must execute first.
- Make the type flow between Functions easy to understand.
- Avoid overly complex chains when a straightforward implementation would be more readable.
Interview Insight
Interview shortcut: Function composition combines multiple Functions into a single transformation. andThen() executes the current Function first, while compose() executes the supplied Function first. The output type of one Function must be compatible with the input type of the next.
Quick Revision
| Concept | Key Point |
|---|---|
| Function Composition | Combines multiple transformations into one pipeline |
| andThen() | Current Function executes first |
| compose() | Supplied Function executes first |
| Type Compatibility | Previous output must match the next input |
| Function.identity() | Returns the input unchanged |
| Main Benefit | Reusable, focused, and composable transformations |
Final Takeaway
Function composition turns individual transformations into a clean processing pipeline. Instead of creating one complicated operation, you can build several focused Functions and connect them with andThen() or compose(). Once you understand the execution order and type flow, composition becomes a practical tool for writing reusable, readable, and maintainable Java code.
