Hook: Interviewers love Consumer because it checks whether you know how Java models ‘do this, don’t return anything’ work.
Question: What is the Consumer interface in Java?
Answer: Consumer<T> is a functional interface in java.util.function that takes one value of type T and returns nothing. Its single method is accept(T t), so you use it for actions like printing, logging, saving, or updating state. In Java 8, lambdas and method references can be assigned to it, which makes code shorter and cleaner.
Interview-Ready Answer: I use Consumer when I want to perform an action on a value without producing a result. It is a functional interface with one method, accept, so I can pass lambdas like name -> System.out.println(name) or method references like System.out::println. A nice detail is that andThen lets me chain actions, and the second one runs only if the first finishes normally.
Consumer<T> lives in java.util.function. It is a functional interface, meaning it has exactly one abstract method, so a lambda can stand in for an implementation. The method is void accept(T t): you give it one value, it does something, and it does not hand back a result.
Consumer<String>, the compiler knows what shape the lambda must have.s -> System.out.println(s), is converted into an object that implements Consumer<String>. In the JVM, this is usually built with invokedynamic, which lets the runtime create the implementation efficiently.accept, the lambda body runs with the supplied argument.andThen, Java runs the first consumer, then the second one, but only if the first one finishes normally.Use Consumer whenever you need a callback for an action: forEach, logging, validation plus update, event listeners, or post-processing. Do not use it when you need a transformed value; that is the job of Function<T,R>.
| Type | Input | Returns | Best for |
|---|---|---|---|
| Consumer | One | Nothing | Action |
| Function | One | Something | Transform |
| Predicate | One | Boolean | Test |
| Supplier | None | Something | Provide |
| BiConsumer | Two | Nothing | Two-part action |
accept itself is O(1); the real cost is whatever your lambda does. Chaining with andThen adds one more call per consumer, so it is still tiny unless each action does I/O. Watch for nulls: andThen(null) throws NullPointerException, and if the first consumer throws an exception, the second never runs. For hot loops on primitives, Java also gives you IntConsumer, LongConsumer, and DoubleConsumer to avoid boxing, which means wrapping primitives into objects like Integer and paying extra memory and CPU.
Real-World Example: In a checkout service, one Consumer<Order> might write an audit log, and another might publish an event to Kafka after the order is saved. A developer once chained those actions and then reused the same consumer inside a parallel stream with a shared ArrayList. The result was intermittent ConcurrentModificationException errors, missing audit entries, and support tickets from users who saw ‘order placed’ in the UI but no confirmation email. The lesson: a Consumer is great for side effects, but the side effect must be thread-safe and should not rely on shared mutable state unless you protect it.
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class ConsumerInterfaceDemo {
public static void main(String[] args) {
// A Consumer performs an action and returns nothing.
Consumer<String> greet = name -> System.out.println("Hello, " + name);
Consumer<String> shout = name -> System.out.println("Uppercase: " + name.toUpperCase());
// andThen creates a new Consumer that runs the first action, then the second.
Consumer<String> combined = greet.andThen(shout);
combined.accept("Java");
// Consumer is commonly used with forEach because forEach expects a Consumer.
List<String> names = Arrays.asList("Ada", "Linus");
System.out.println("Names from forEach:");
names.forEach(System.out::println);
// Edge case: andThen does not accept null. This is a fast fail, which is good.
try {
greet.andThen(null);
} catch (NullPointerException ex) {
System.out.println("Caught expected exception from andThen(null): " + ex);
}
// Edge case: the Consumer itself can validate input and fail on bad data.
Consumer<Integer> requirePositive = n -> {
if (n <= 0) {
throw new IllegalArgumentException("Number must be positive, got " + n);
}
System.out.println("Accepted number: " + n);
};
requirePositive.accept(5);
try {
requirePositive.accept(-1);
} catch (IllegalArgumentException ex) {
System.out.println("Caught expected validation error: " + ex.getMessage());
}
}
}Follow-up & Tricky Questions:
Consumer performs an action and returns void; Function<T,R> transforms input into an output. If you need a result, choose Function; if you just need side effects, choose Consumer.forEach need a callback to act on each element. A Consumer fits perfectly when you want to print, collect metrics, or trigger a side effect.Consumer<T> uses objects, so passing primitives like int causes boxing into Integer. For tight loops, prefer IntConsumer, LongConsumer, or DoubleConsumer to avoid that overhead.accept, and it is defined to return nothing. If someone says it returns something, that is a category error with Function.Runnable takes no input and returns nothing, while Consumer takes one input and returns nothing. Think of Runnable as ‘do a task’ and Consumer as ‘do something to this value’.NullPointerException immediately. That is deliberate fail-fast behavior so bugs show up early instead of silently dropping work.Common Mistakes:
Consumer for transformations. Correction: if you need to produce a new value, use Function or a stream map.andThen continues after failure. Correction: the second consumer runs only if the first completes normally.IntConsumer in hot code paths.Memory Hook: Think of Consumer like a power socket: you plug in one value, it uses that value to do work, and nothing comes back out.
Cheat Sheet:
Consumer<T> = one input, no return.accept(T t).andThen chains actions in order.forEach with consumers.Practice Tasks:
Consumer<String> that prints a username with a prefix.andThen and test what happens when the first throws.forEach on a list of items with a method reference like System.out::println.