Definition
- Consumer
has a single accept(T)method performing a side-effect with no return value. - .andThen(after) composes two consumers: both run on the same input, in sequence.
- No value passes between them; both act on the original input independently.
- If the first throws, the second does not run.
- Useful for sequencing multiple side-effects in a
forEach.
For each new hire at the National Bank of India, Priya wants two things done: log an audit entry and send a welcome email. She glues the two actions with andThen so every employee gets both, in order.
Understanding the Concepts
How do you chain two consumers?
Define each consumer separately, then compose with .andThen().
package com.bank.hr;
import java.util.*;
import java.util.function.Consumer;
public class ConsumerChain {
public static void main(String[] args) {
List<Employee> employees = List.of(
new Employee("Rajesh", "Loans"),
new Employee("Priya", "IT")
);
Consumer<Employee> logAudit = e ->
System.out.println("AUDIT: " + e.getName());
Consumer<Employee> sendWelcome = e ->
System.out.println("WELCOME: " + e.getName());
employees.forEach(logAudit.andThen(sendWelcome));
}
}
AUDIT: Rajesh
WELCOME: Rajesh
AUDIT: Priya
WELCOME: Priya
How is Consumer.andThen different from Function.andThen?
| Feature | Consumer.andThen | Function.andThen |
|---|---|---|
| Return type | void | R (result of second function) |
| Data flow | Both act on original input | First's result feeds the second |
| Use case | Multiple side-effects | Transform pipeline |
What happens if the first consumer throws?
The second consumer does not run; the exception propagates.
Consumer<Employee> failFirst = e -> {
throw new RuntimeException("Audit failed for " + e.getName());
};
Consumer<Employee> neverRuns = e ->
System.out.println("This never prints");
// The second consumer never executes
employees.forEach(failFirst.andThen(neverRuns));
How do you ensure both actions run regardless of failure?
Wrap each action in its own try-catch.
Consumer<Employee> resilient = e -> {
try { logAudit.accept(e); }
catch (Exception ex) { System.err.println("Audit failed: " + ex); }
try { sendWelcome.accept(e); }
catch (Exception ex) { System.err.println("Welcome failed: " + ex); }
};
employees.forEach(resilient);
What are common mistakes?
Mistake: cramming multiple statements into one lambda instead of composing consumers.
// WRONG: hard to test and reuse
employees.forEach(e -> {
System.out.println("AUDIT: " + e.getName());
System.out.println("WELCOME: " + e.getName());
// more actions...
});
// CORRECT: named, composable consumers
employees.forEach(logAudit.andThen(sendWelcome));
What about BiConsumer for Map.forEach?
BiConsumer takes two arguments, used with Map.forEach.
import java.util.Map;
import java.util.function.BiConsumer;
Map<String, String> deptHeads = Map.of("Loans", "Rajesh", "IT", "Priya");
BiConsumer<String, String> logEntry = (dept, head) ->
System.out.println(dept + " head: " + head);
deptHeads.forEach(logEntry);
What should a senior engineer know?
- Consumer chaining is ideal for sequencing side-effects: audit, notify, log, count.
- The composed consumer runs left to right and stops on the first exception.
- For independent, must-attempt effects, do not rely on andThen; use explicit try-catch.
- Named consumers are testable in isolation, just like named predicates and functions.
What are common interview follow-up questions?
- "Can you chain three consumers?" Yes: a.andThen(b).andThen(c).
- "What is BiConsumer?" A consumer that takes two arguments, used with Map.forEach.
- "Can a consumer return a value?" No; use Function if you need a return value.
Visual Overview
forEach element: Employee("Rajesh")
│
│ logAudit.accept(e)
▼
AUDIT: Rajesh
│
│ sendWelcome.accept(e) (andThen)
▼
WELCOME: Rajesh
Both act on the same original element.
No value passes between them.
Key Takeaway
A Consumer performs a side-effect and returns nothing, and .andThen() composes two consumers so both run on the same element in sequence. Define each action as a named consumer, then compose with andThen for a clean forEach that logs an audit and sends a welcome per employee.
Unlike Function.andThen, no value passes between consumers; both act on the original input. If the first consumer throws, the second does not run. For independent effects that must all attempt, use try-catch per action.
At the National Bank of India, consumer chaining kept the onboarding forEach readable while performing audit logging and notification. Named consumers are testable in isolation, just like named predicates.
Remember one sentence: Consumer.andThen(other) runs both consumers on the same element in order, and stops on the first exception.
---