Composing Functions in Java

Naveen AnamaneniUpdated · 4 min read

Composing functions means joining small functions together to build a bigger one. Java's functional interfaces have default methods for this: Function has andThen and compose, Predicate has and, or and negate, and Consumer has andThen. Instead of writing one large lambda, you write several small, well-named pieces and combine them. In this lesson, you will learn each composition method with an example.

Composition Methods

InterfaceMethodDescription
Function<T, R>andThen(after)It applies this function, then after to the result.
Function<T, R>compose(before)It applies before first, then this function.
Functionstatic identity()It returns a function that returns its input unchanged.
Predicate<T>and(other)It is true only if both predicates are true.
Predicate<T>or(other)It is true if either predicate is true.
Predicate<T>negate()It reverses the result.
Predicatestatic not(p)It returns the negation of p (Java 11).
Consumer<T>andThen(after)It runs this consumer, then after, on the same value.

Example: andThen and compose

Let's see an example that shows the order in which composed functions run.

FunctionChain.java▶ Try it
import java.util.function.Function;
 
public class FunctionChain {
    public static void main(String[] args) {
        Function<Integer, Integer> addTen = x -> x + 10;
        Function<Integer, Integer> doubleIt = x -> x * 2;
 
        System.out.println("addTen.andThen(doubleIt): "
            + addTen.andThen(doubleIt).apply(5));
        System.out.println("addTen.compose(doubleIt): "
            + addTen.compose(doubleIt).apply(5));
 
        Function<String, String> trim = String::strip;
        Function<String, String> clean = trim.andThen(String::toLowerCase)
                                             .andThen(s -> s.replace(' ', '-'));
        System.out.println(clean.apply("  Navika Anamaneni  "));
    }
}
Output
addTen.andThen(doubleIt): 30
addTen.compose(doubleIt): 20
navika-anamaneni

Explanation: addTen.andThen(doubleIt) runs addTen first: (5 + 10) x 2 = 30. addTen.compose(doubleIt) runs doubleIt first: (5 x 2) + 10 = 20. The clean function chains three small steps into a "slug maker" that trims, lowercases and replaces spaces.

Example: Combining Predicates

PredicateChain.java▶ Try it
import java.util.List;
import java.util.function.Predicate;
 
public class PredicateChain {
    public static void main(String[] args) {
        Predicate<String> startsWithA = s -> s.startsWith("A");
        Predicate<String> longName = s -> s.length() > 7;
 
        List<String> names =
            List.of("Navika", "Srihitha", "Advitha", "Anamaneni");
 
        System.out.println("A and long: "
            + names.stream().filter(startsWithA.and(longName)).toList());
        System.out.println("A or long:  "
            + names.stream().filter(startsWithA.or(longName)).toList());
        System.out.println("not A:      "
            + names.stream().filter(startsWithA.negate()).toList());
        System.out.println("not long:   "
            + names.stream().filter(Predicate.not(longName)).toList());
    }
}
Output
A and long: [Anamaneni]
A or long:  [Srihitha, Advitha, Anamaneni]
not A:      [Navika, Srihitha]
not long:   [Navika, Advitha]

Explanation: and keeps names that start with A and have more than 7 letters, which is only Anamaneni. or keeps names that satisfy either test. negate() and Predicate.not(...) both flip a test; not is handy with method references, as in filter(Predicate.not(String::isBlank)).

Example: Chaining Consumers

ConsumerChain.java▶ Try it
import java.util.function.Consumer;
 
public class ConsumerChain {
    public static void main(String[] args) {
        Consumer<String> log = s -> System.out.println("LOG: " + s);
        Consumer<String> greet = s -> System.out.println("Hello, " + s);
        log.andThen(greet).accept("Advitha");
    }
}
Output
LOG: Advitha
Hello, Advitha

Explanation: log.andThen(greet) creates one consumer that runs log and then greet with the same input.

Common Mistakes

Mixing up andThen and compose. Read andThen left to right; compose right to left.

Composing predicates with different types. Predicate<String>.and(Predicate<Integer>) doesn't compile.

Forgetting that Function.identity() is a function, not a value. Use it where a Function is expected, such as toMap(Function.identity(), ...).

Practice

  1. Easy: Compose Function<Double, Double> steps that add 18% tax and then subtract a discount of 50, and apply them to a price.
  2. Harder: Build predicates isAdult, hasTicket and isVip for a record Guest(String name, int age, boolean ticket, boolean vip), and combine them so that adults with a ticket, or any VIP, may enter.

Quick check: Composing functions

4 questions
1In f.andThen(g), which runs first?
2In f.compose(g), which runs first?
3What does p.negate() return?
4Which method combines two predicates so either may be true?

Key Takeaways

Was this tutorial helpful?

Lesson 309 of 482