skip to content

What does Consumer.andThen do, and how does composing Consumers differ from composing Functions?

level: middleimportance: should knowfreq 45%

answer

  1. Consumer = accept(T) -> void, pure side effect
  2. c1.andThen(c2): run both on the SAME input, in order
  3. Function.andThen feeds output forward; Consumer has no output
  4. No Consumer.compose (nothing to chain backward)
  5. If first consumer throws, second is skipped

basics

~20 s

Consumer takes a value and returns nothing (it does a side effect). c1.andThen(c2) makes one Consumer that runs c1 then c2 on the SAME input — it doesn't pass a result along, because Consumers produce no result.

solid answer

~40 s

Consumer<T> has a single method void accept(T) — it performs a side effect (log, print, store) and returns nothing. Its andThen default method composes side effects: c1.andThen(c2) returns a Consumer that calls c1.accept(t) then c2.accept(t) on the same input t, in sequence. The key contrast with Function.andThen is data flow: Function.andThen feeds f's output into g (g sees a transformed value), whereas Consumer.andThen has no output to feed, so both consumers receive the identical original input. There's no compose on Consumer, because there is no return value to chain backward. Consumer has no short-circuiting — both always run (unless the first throws, which propagates and skips the second). It's useful to attach multiple actions to one event, e.g. log.andThen(audit).andThen(notify), keeping each side effect a small, separate Consumer.

go deeper

for a junior

Knows Consumer.andThen runs two side-effecting actions in order on the same value.

for a middle

Explains why both consumers receive the same input (no return value to forward) and why Consumer has no compose.

for a senior

Contrasts value-forwarding (Function) vs action-sequencing (Consumer) composition, and reasons about exception propagation skipping the second consumer.

for a principal

Uses Consumer composition to assemble independent side effects (log/audit/notify) cleanly, but recognizes the lack of error isolation and ordering coupling, choosing explicit orchestration when failure handling between steps matters.

## What a Consumer is `java.util.function.Consumer<T>` is a **functional interface** with one abstract method, `void accept(T t)`. The name says it all: it *consumes* a value and returns **nothing**. Its whole point is the **side effect** — printing, logging, adding to a collection, sending a message. (Compare: a `Function` returns a value; a `Consumer` does not.) ## Consumer.andThen ``` default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } ``` `c1.andThen(c2)` returns a **new Consumer** that, when applied to a value `t`, runs `c1.accept(t)` first and then `c2.accept(t)` — **both on the same `t`**, in that order. It's a way to bundle several actions into one. Example: ``` Consumer<String> log = s -> System.out.println("LOG: " + s); Consumer<String> store = s -> cache.add(s); Consumer<String> both = log.andThen(store); both.accept("hi"); // prints LOG: hi, then adds "hi" to cache ``` ## The crucial contrast with Function.andThen This is the heart of the question. Both interfaces have an `andThen`, but the **data flow differs**: - **`Function.andThen`** chains a *value*: `f.andThen(g)` computes `g(f(x))`. The output of `f` becomes the input of `g`. Each stage transforms the data. - **`Consumer.andThen`** chains *actions*: `c1.andThen(c2)` runs `c1(t)` then `c2(t)`. There is **no output** to pass along, so `c2` receives the **same original `t`** that `c1` did, not a transformed value. The reason is structural: a `Consumer` returns `void`, so there is nothing to feed forward. ## Why there's no Consumer.compose `compose` exists on `Function` to run the argument function first and feed its **result** into the receiver. Since a `Consumer` has no result, "feed the result backward" is meaningless — so `Consumer` has **no `compose` method**, only `andThen`. ## Ordering, exceptions, and null - **Order is guaranteed**: `c1` runs strictly before `c2`. - **No short-circuiting**: both always run — unless `c1.accept(t)` throws, in which case the exception propagates and `c2` is **not** run. - **Null safety**: `andThen` calls `Objects.requireNonNull(after)`, so passing a null consumer fails fast with an NPE at composition time. ## Why it matters `Consumer.andThen` lets you keep each side effect as a small, independently testable unit and then combine them at the point of use (`log.andThen(audit).andThen(notify)`), instead of one giant lambda doing everything. It reflects a general principle: composition combines behaviors, but *how* it combines depends on whether the pieces produce values (Function) or just act (Consumer).

  • Why does Function have a compose method but Consumer does not?
    compose feeds the argument function's RESULT into the receiver. A Consumer returns void — there is no result to feed backward — so compose would be meaningless and is omitted.
  • In log.andThen(store).accept(x), if log throws, does store run?
    No. The exception from log propagates out of accept and store is never reached; the composed consumer is not exception-isolating.

saying these in an interview costs you the question

  • Saying Consumer.andThen passes c1's result to c2 — Consumers have no result; both get the same input.
  • Claiming Consumer has a compose method.
  • Thinking the two consumers run in unspecified order — c1 always runs before c2.
  • Assuming composition is lazy AND that both run even if the first throws — an exception in c1 skips c2.

context