Java BiConsumer Interface
Last modified: April 16, 2025
The java.util.function.BiConsumer interface represents an operation
that accepts two input arguments and returns no result. It is a functional
interface with a single abstract method accept. BiConsumer is used
for operations that need to process two values without returning anything.
BiConsumer is part of Java's functional programming utilities added
in Java 8. It enables side-effect operations on two input parameters. Common
uses include iterating through maps or performing operations on pairs of values.
BiConsumer Interface Overview
BiConsumer interface contains one abstract method and one default
method. The key method accept performs the operation on the inputs.
The andThen method enables chaining multiple BiConsumers.
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after);
}
The code above shows the structure of BiConsumer interface. It uses
generics where T and U are input types. The interface is annotated with
@FunctionalInterface to indicate its single abstract method nature.
Basic BiConsumer Usage
The simplest way to use BiConsumer is with lambda expressions. We define how to process two input values in the accept method. The example prints key-value pairs.
package com.zetcode;
import java.util.function.BiConsumer;
public class Main {
public static void main(String[] args) {
// Define a BiConsumer that prints two values
BiConsumer<String, Integer> printPair = (key, value) ->
System.out.println("Key: " + key + ", Value: " + value);
// Use the BiConsumer
printPair.accept("age", 30);
printPair.accept("score", 95);
// BiConsumer using method reference
BiConsumer<String, String> concatPrinter = System.out::println;
concatPrinter.accept("Hello", "World");
}
}
This example demonstrates basic BiConsumer usage with lambda and method reference. The printPair takes String and Integer inputs and prints them. Method reference provides concise syntax for existing methods that match BiConsumer signature.
BiConsumer with Map Iteration
BiConsumer is commonly used with Map's forEach method. The Map's
key-value pairs perfectly match BiConsumer's two input parameters. This enables
clean iteration over map entries.
package com.zetcode;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
public class Main {
public static void main(String[] args) {
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 85);
scores.put("Bob", 92);
scores.put("Charlie", 78);
// BiConsumer to print map entries
BiConsumer<String, Integer> printEntry =
(name, score) -> System.out.println(name + ": " + score);
// Iterate map with BiConsumer
scores.forEach(printEntry);
// Direct lambda in forEach
scores.forEach((k, v) -> {
if (v > 80) {
System.out.println(k + " passed");
}
});
}
}
This example shows BiConsumer usage with Map.forEach. We first define a separate BiConsumer for printing entries, then use direct lambda for conditional logic. Map iteration becomes very expressive with BiConsumer.
Chaining BiConsumers with andThen
The andThen method allows chaining multiple BiConsumers to perform
sequential operations. Each BiConsumer in the chain receives the same input
parameters.
package com.zetcode;
import java.util.function.BiConsumer;
public class Main {
public static void main(String[] args) {
// First BiConsumer logs the operation
BiConsumer<String, Integer> logger =
(item, qty) -> System.out.println("Processing: " + item + " x" + qty);
// Second BiConsumer processes the order
BiConsumer<String, Integer> processor =
(item, qty) -> System.out.println("Ordered " + qty + " of " + item);
// Chain the BiConsumers
BiConsumer<String, Integer> orderHandler = logger.andThen(processor);
// Use the chained BiConsumer
orderHandler.accept("Laptop", 2);
orderHandler.accept("Mouse", 5);
}
}
This example demonstrates BiConsumer chaining with andThen. The
orderHandler executes both logging and processing for each input pair. Both
BiConsumers receive the same parameters when the chain is executed.
BiConsumer for Object Modification
BiConsumer can be used to modify object properties based on two
input parameters. This is useful for batch updates or configuration operations.
package com.zetcode;
import java.util.function.BiConsumer;
class Product {
String name;
double price;
Product(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return name + ": $" + price;
}
}
public class Main {
public static void main(String[] args) {
// BiConsumer to update product price with discount
BiConsumer<Product, Double> applyDiscount =
(product, discount) -> {
product.price = product.price * (1 - discount/100);
System.out.println("Applied " + discount + "% discount");
};
Product laptop = new Product("Laptop", 999.99);
Product phone = new Product("Phone", 699.99);
applyDiscount.accept(laptop, 10.0);
applyDiscount.accept(phone, 15.0);
System.out.println(laptop);
System.out.println(phone);
}
}
This example shows BiConsumer modifying object state. The applyDiscount BiConsumer takes a Product and discount percentage, then updates the product's price. This pattern is useful for applying consistent modifications.
BiConsumer in Stream Operations
BiConsumer can be used in stream operations that process pairs of
values. While not as common as Function in streams, it's useful for terminal
operations with side effects.
package com.zetcode;
import java.util.List;
import java.util.function.BiConsumer;
public class Main {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Charlie");
List<Integer> scores = List.of(85, 92, 78);
// BiConsumer to print name-score pairs
BiConsumer<String, Integer> printResult =
(name, score) -> System.out.println(name + " scored " + score);
// Process parallel lists with BiConsumer
if (names.size() == scores.size()) {
for (int i = 0; i < names.size(); i++) {
printResult.accept(names.get(i), scores.get(i));
}
}
// More complex BiConsumer
BiConsumer<String, Integer> resultAnalyzer = (name, score) -> {
String status = score >= 80 ? "Pass" : "Fail";
System.out.println(name + ": " + score + " (" + status + ")");
};
System.out.println("\nAnalysis:");
for (int i = 0; i < names.size(); i++) {
resultAnalyzer.accept(names.get(i), scores.get(i));
}
}
}
This example demonstrates BiConsumer processing parallel lists. We define two BiConsumers - one for simple printing and another for more complex analysis. This pattern is useful when processing related data in separate collections.
Primitive Specializations of BiConsumer
Java provides specialized versions of BiConsumer for primitive types to avoid autoboxing overhead. These include ObjIntConsumer, ObjLongConsumer, and ObjDoubleConsumer.
package com.zetcode;
import java.util.function.ObjIntConsumer;
import java.util.function.ObjDoubleConsumer;
public class Main {
public static void main(String[] args) {
// ObjIntConsumer example
ObjIntConsumer<String> printWithNumber =
(s, i) -> System.out.println(s + ": " + i);
printWithNumber.accept("Count", 42);
// ObjDoubleConsumer example
ObjDoubleConsumer<String> temperatureLogger =
(location, temp) -> System.out.printf("%s: %.1f°C%n", location, temp);
temperatureLogger.accept("New York", 22.5);
// Using BiConsumer with boxed primitives
BiConsumer<String, Integer> boxedConsumer =
(s, i) -> System.out.println(s.repeat(i));
boxedConsumer.accept("Hi ", 3);
}
}
This example shows primitive specializations of BiConsumer. ObjIntConsumer and ObjDoubleConsumer avoid boxing overhead when working with primitives. The last example shows regular BiConsumer with boxed Integer for comparison.
Combining BiConsumer with Other Functional Interfaces
BiConsumer can be combined with other functional interfaces to
create more complex operations. This example shows pairing it with Function
for data transformation before consumption.
package com.zetcode;
import java.util.function.BiConsumer;
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
// Function to calculate area
Function<Double, Double> areaCalculator = radius -> Math.PI * radius * radius;
// BiConsumer to print formatted results
BiConsumer<String, Double> resultPrinter =
(label, value) -> System.out.printf("%s: %.2f%n", label, value);
// Process radius values
double[] radii = {1.0, 2.5, 3.0};
for (double r : radii) {
double area = areaCalculator.apply(r);
resultPrinter.accept("Radius " + r + " area", area);
}
// More complex combination
BiConsumer<String, Function<Double, Double>> calculator =
(name, func) -> {
double result = func.apply(10.0);
System.out.println(name + " at 10.0: " + result);
};
calculator.accept("Square", x -> x * x);
calculator.accept("Cube", x -> x * x * x);
}
}
This example demonstrates combining BiConsumer with Function. We first use them separately for calculation and printing, then create a BiConsumer that accepts a Function as its second parameter. This shows the flexibility of functional interfaces.
Source
Java BiConsumer Interface Documentation
In this article, we've covered the essential methods and features of the Java BiConsumer interface. Understanding these concepts is crucial for functional programming and collection processing in modern Java applications.
Author
List all Java tutorials.