Consumer
Consumer<T> is a functional interface in java.util.function that represents an operation that accepts a single input argument and returns no result. It is the standard abstraction for side-effect-oriented operations — printing, logging, persisting, modifying in-place, or any action that consumes a value without producing a new one. Consumer<T> declares one abstract method, accept(T t), and one default method, andThen(Consumer<? super T> after), which composes two consumers sequentially: the second consumer receives the same input as the first, enabling chaining of multiple side effects on the same value. The java.util.function package also provides primitive specializations — IntConsumer, LongConsumer, DoubleConsumer — that avoid boxing overhead, and BiConsumer<T, U> for operations on two arguments. Consumer is used throughout the Stream API (forEach), the Collections API (Iterable.forEach, Map.forEach), and is the standard type for callback and event-handler parameters. This entry covers the full Consumer API including andThen composition, all primitive specializations and BiConsumer, the distinction between Consumer and other functional interfaces, common usage patterns in streams and collections, and how to write effective Consumer implementations.
Consumer<T> — accept() and andThen() Composition
// ── Basic Consumer lambda ─────────────────────────────────────────────
import java.util.function.*;
Consumer<String> printer = s -> System.out.println(s);
Consumer<String> upperPrinter = s -> System.out.println(s.toUpperCase());
printer.accept("hello"); // prints: hello
upperPrinter.accept("hello"); // prints: HELLO
// ── Method reference as Consumer ─────────────────────────────────────
Consumer<String> mrefPrinter = System.out::println; // instance method ref on PrintStream
Consumer<String> mrefUpper = String::toUpperCase; // WRONG — toUpperCase() returns String, not void
// Correct method reference: static method with void return + one parameter:
Consumer<String> logger = MyLogger::log; // static void log(String msg)
// Instance method of the input object:
Consumer<List<String>> listClearer = List::clear; // void clear() called on the list
List<String> words = new ArrayList<>(List.of("a","b","c"));
listClearer.accept(words);
System.out.println(words); // []
// ── andThen: sequential composition ──────────────────────────────────
Consumer<String> validate = s -> {
if (s == null || s.isEmpty()) throw new IllegalArgumentException("Empty string");
};
Consumer<String> log = s -> System.out.println("Processing: " + s);
Consumer<String> persist = s -> database.save(s);
// Compose into a single Consumer:
Consumer<String> pipeline = validate.andThen(log).andThen(persist);
pipeline.accept("Alice"); // validate, then log, then persist — left to right
// Exception in first consumer stops the chain:
try {
pipeline.accept(""); // validate throws → log and persist NOT called
} catch (IllegalArgumentException e) {
System.out.println("Validation failed: " + e.getMessage());
}
// ── andThen with ? super T: compose with more general consumer ────────
Consumer<String> stringConsumer = s -> System.out.println("String: " + s);
Consumer<Object> objectConsumer = o -> System.out.println("Object: " + o.getClass().getSimpleName());
// Consumer<String>.andThen(Consumer<? super String>) = Consumer<String>.andThen(Consumer<Object>)
Consumer<String> composed = stringConsumer.andThen(objectConsumer);
composed.accept("test");
// String: test
// Object: String
// ── Building a processing pipeline with andThen ───────────────────────
Consumer<User> auditLog = user -> audit.log("Access: " + user.id());
Consumer<User> updateCache = user -> cache.put(user.id(), user);
Consumer<User> sendWelcome = user -> emailService.sendWelcome(user.email());
Consumer<User> onNewUser = auditLog.andThen(updateCache).andThen(sendWelcome);
// Pass the composed consumer anywhere a Consumer<User> is accepted:
newUsers.forEach(onNewUser);Primitive Specializations and BiConsumer
// ── IntConsumer: primitive int, no boxing ─────────────────────────────
IntConsumer printInt = i -> System.out.println("int: " + i);
IntConsumer doubleAndPrint = i -> System.out.println("doubled: " + (i * 2));
printInt.accept(42); // int: 42
doubleAndPrint.accept(42); // doubled: 84
IntConsumer both = printInt.andThen(doubleAndPrint);
both.accept(10);
// int: 10
// doubled: 20
// ── Primitive specialization performance ─────────────────────────────
// Consumer<Integer>: each call boxes the int to Integer → heap allocation
Consumer<Integer> boxedConsumer = i -> System.out.println(i);
boxedConsumer.accept(42); // 42 (int) → Integer.valueOf(42) → auto-unboxed for println
// IntConsumer: no boxing — int passed directly
IntConsumer primitiveConsumer = i -> System.out.println(i);
primitiveConsumer.accept(42); // no allocation
// In a loop over 1,000,000 ints:
// Consumer<Integer>: 1M Integer allocations → GC pressure
// IntConsumer: 0 allocations → significantly faster
// ── LongConsumer and DoubleConsumer ──────────────────────────────────
LongConsumer printLong = l -> System.out.printf("long: %d%n", l);
DoubleConsumer printDouble = d -> System.out.printf("double: %.4f%n", d);
printLong.accept(System.currentTimeMillis());
printDouble.accept(Math.PI);
// ── BiConsumer<T, U>: two-argument consumer ───────────────────────────
BiConsumer<String, Integer> printKeyValue = (key, value) ->
System.out.printf("%-20s: %d%n", key, value);
printKeyValue.accept("Count", 42);
printKeyValue.accept("Total", 1000);
printKeyValue.accept("Average", 25);
// ── BiConsumer with Map.forEach ───────────────────────────────────────
Map<String, Integer> scores = Map.of(
"Alice", 95, "Bob", 87, "Carol", 92);
// Map.forEach signature: void forEach(BiConsumer<? super K, ? super V> action)
scores.forEach((name, score) ->
System.out.printf("%-10s scored %d%n", name, score));
// BiConsumer composition with andThen:
BiConsumer<String, Integer> log = (k, v) -> System.out.println("Logging: " + k + "=" + v);
BiConsumer<String, Integer> cache = (k, v) -> localCache.put(k, v);
BiConsumer<String, Integer> both = log.andThen(cache);
scores.forEach(both); // log AND cache each entry
// ── ObjIntConsumer: reference + primitive bivariate ───────────────────
ObjIntConsumer<List<String>> insertAt = (list, index) ->
list.add(index, "INSERTED");
List<String> data = new ArrayList<>(List.of("a", "b", "c"));
insertAt.accept(data, 1);
System.out.println(data); // [a, INSERTED, b, c]Consumer in Stream API, Collections, and Design Patterns
// ── Consumer in Stream.forEach ────────────────────────────────────────
List<String> names = List.of("Alice", "Bob", "Carol", "Dave");
// lambda Consumer in forEach:
names.stream().forEach(name -> System.out.println("Hello, " + name));
// method reference Consumer in forEach:
names.stream().forEach(System.out::println);
// ── Stream.peek: Consumer for debugging (intermediate) ────────────────
List<Integer> result = List.of(1, 2, 3, 4, 5).stream()
.peek(n -> System.out.println("Before filter: " + n))
.filter(n -> n % 2 == 0)
.peek(n -> System.out.println("After filter: " + n))
.collect(Collectors.toList());
// Before filter: 1
// Before filter: 2 After filter: 2
// Before filter: 3
// Before filter: 4 After filter: 4
// Before filter: 5
// ── Iterable.forEach on Collections ──────────────────────────────────
Map<String, List<String>> groupedData = new HashMap<>();
groupedData.put("fruits", List.of("apple", "banana"));
groupedData.put("veggies", List.of("carrot", "pea"));
// Map.forEach: BiConsumer<K, V>
groupedData.forEach((category, items) -> {
System.out.println(category + ": " + items);
});
// ── Observer pattern with Consumer ───────────────────────────────────
public class EventBus<T> {
private final List<Consumer<T>> listeners = new ArrayList<>();
public void subscribe(Consumer<T> listener) {
listeners.add(listener);
}
public void publish(T event) {
listeners.forEach(listener -> listener.accept(event));
}
}
EventBus<String> bus = new EventBus<>();
bus.subscribe(msg -> System.out.println("Logger: " + msg));
bus.subscribe(msg -> auditService.record(msg));
bus.subscribe(msg -> notificationService.send(msg));
bus.publish("User logged in");
// Logger: User logged in
// (audit recorded)
// (notification sent)
// ── Builder configure-with-Consumer idiom ────────────────────────────
public class HttpRequest {
private String method, url, body;
private Map<String, String> headers = new HashMap<>();
private HttpRequest() {}
public static HttpRequest create(Consumer<HttpRequest> configurator) {
HttpRequest req = new HttpRequest();
configurator.accept(req); // caller configures the request
req.validate();
return req;
}
public HttpRequest method(String m) { this.method = m; return this; }
public HttpRequest url(String u) { this.url = u; return this; }
public HttpRequest body(String b) { this.body = b; return this; }
public HttpRequest header(String k, String v) { headers.put(k, v); return this; }
private void validate() { Objects.requireNonNull(method); Objects.requireNonNull(url); }
}
HttpRequest req = HttpRequest.create(r -> r
.method("POST")
.url("/api/users")
.header("Content-Type", "application/json")
.body("{"name":"Alice"}")
);