A Consumer is a functional interface in Java used when you want to accept a value and perform an action without returning a result. In simple terms, a Consumer answers the question: "What should I do with this value?"
Printing a name, saving an object, logging information, sending a notification, or updating a record are all examples of operations that can be represented by a Consumer.
What Is Consumer?
The Consumer<T> interface belongs to the java.util.function package.
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
The interface contains one abstract method named accept(). It receives one value and returns nothing.
Remember: Predicate asks a question and returns boolean. Consumer receives a value and performs an action, but returns void.
Basic Consumer Example
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
Consumer<String> printer =
message -> System.out.println(message);
printer.accept("Hello, Java!");
}
}
The lambda receives a string and prints it. The accept() method supplies the actual value to the Consumer.
Understanding Consumer<T>
The generic type T represents the type of value that the Consumer accepts.
| Consumer | Input | Return Value | Method |
|---|---|---|---|
| Consumer<String> | String | void | accept() |
| Consumer<Integer> | Integer | void | accept() |
| Consumer<Student> | Student | void | accept() |
Unlike Function, a Consumer does not transform its input into another value. Unlike Predicate, it does not produce a boolean result.
Consumer with Integer
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
Consumer<Integer> display =
number -> System.out.println("Number: " + number);
display.accept(100);
}
}
The Consumer receives an integer and performs an output operation. There is no returned value.
Consumer with Custom Objects
Consumers become especially useful when processing application objects.
import java.util.function.Consumer;
class Student {
String name;
int marks;
Student(String name, int marks) {
this.name = name;
this.marks = marks;
}
}
public class Main {
public static void main(String[] args) {
Consumer<Student> displayStudent =
student -> System.out.println(
student.name + " - " + student.marks
);
Student student = new Student("Ravi", 85);
displayStudent.accept(student);
}
}
The Consumer knows how to display a Student object. The calling code only needs to provide the object.
Consumer with Collections
One of the most common places you will encounter Consumer is the forEach() method.
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList(
"Ravi",
"Anita",
"Priya",
"Amit"
);
names.forEach(
name -> System.out.println(name)
);
}
}
The forEach() operation accepts a Consumer. For every element in the list, the lambda receives that element and performs the specified action.
Storing a Consumer in a Variable
A Consumer can be stored in a variable and reused.
Consumer<String> printer =
text -> System.out.println("Value: " + text);
printer.accept("Java");
printer.accept("Spring Boot");
printer.accept("SQL");
This is useful when the same operation needs to be performed in several places.
Passing Consumer to a Method
A method can accept a Consumer as an argument. This allows the caller to decide what action should happen.
import java.util.function.Consumer;
public class Main {
static void process(String value, Consumer<String> action) {
action.accept(value);
}
public static void main(String[] args) {
process("Java",
text -> System.out.println(
"Processing: " + text
)
);
}
}
The process() method does not contain a fixed action. The behavior is supplied by the caller through the Consumer.
Consumer with Multiple Statements
A Consumer can contain multiple statements when a block lambda is used.
Consumer<String> process =
text -> {
System.out.println("Received: " + text);
System.out.println("Length: " + text.length());
};
process.accept("Lambda");
Because Consumer's method returns void, there is no return statement in the lambda.
Consumer and Side Effects
Consumers are commonly associated with side effects. A side effect means the operation changes something outside the calculation itself, such as printing output, updating a database, modifying an object, or writing a log.
Consumer<String> logger =
message -> System.out.println(
"LOG: " + message
);
The purpose here is not to calculate a new value. The purpose is to perform an action.
When deciding whether Consumer is appropriate, ask: "Do I need to perform an action but do not need a result back?" If the answer is yes, Consumer is likely a good match.
Consumer Chaining with andThen()
One of the most useful features of Consumer is the andThen() method. It allows two Consumers to be executed sequentially.
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
Consumer<String> first =
text -> System.out.println("First: " + text);
Consumer<String> second =
text -> System.out.println("Second: " + text);
Consumer<String> combined =
first.andThen(second);
combined.accept("Java");
}
}
When combined.accept("Java") executes, the first Consumer runs before the second Consumer.
Chaining More Than Two Consumers
Consumers can be chained multiple times when a sequence of actions needs to be performed.
Consumer<String> first =
text -> System.out.println("Step 1: " + text);
Consumer<String> second =
text -> System.out.println("Step 2: " + text);
Consumer<String> third =
text -> System.out.println("Step 3: " + text);
Consumer<String> workflow =
first.andThen(second).andThen(third);
workflow.accept("Processing");
This technique can be useful when several small actions form a predictable sequence.
Consumer vs Predicate
Both interfaces accept one value, but their purposes are different.
| Feature | Predicate | Consumer |
|---|---|---|
| Input | One value | One value |
| Return type | boolean | void |
| Main method | test() | accept() |
| Purpose | Check a condition | Perform an action |
| Typical use | Filtering | Processing or displaying |
Consumer vs Function
The easiest way to distinguish Consumer from Function is to ask whether you need a result.
| Feature | Consumer | Function |
|---|---|---|
| Input | One value | One value |
| Output | None | One value |
| Method | accept() | apply() |
| Purpose | Perform an action | Transform a value |
Practical Example: Processing Orders
import java.util.*;
import java.util.function.Consumer;
class Order {
int id;
double amount;
Order(int id, double amount) {
this.id = id;
this.amount = amount;
}
}
public class Main {
public static void main(String[] args) {
List<Order> orders = Arrays.asList(
new Order(101, 2500),
new Order(102, 1800),
new Order(103, 4200)
);
Consumer<Order> processOrder =
order -> System.out.println(
"Processing order: " + order.id
);
orders.forEach(processOrder);
}
}
The Consumer represents the action to perform for every order. If the application later needs a different processing behavior, another Consumer can be supplied without changing the collection traversal logic.
Consumer in Real Applications
- Logging application events.
- Displaying objects or values.
- Sending notifications.
- Updating existing objects.
- Processing records from a collection.
- Executing a sequence of operations using andThen().
Common Beginner Mistakes
- Trying to return a value from a Consumer.
- Calling test() instead of accept().
- Using Consumer when the operation actually needs to produce a transformed result.
- Putting too much business logic inside a single Consumer.
- Assuming that every lambda accepting one parameter is automatically a Consumer.
Best Practices
- Use Consumer when an operation needs an input but does not need to return a result.
- Keep Consumer logic small and focused.
- Use meaningful variable names when storing Consumers.
- Use andThen() when sequential actions are easier to understand as separate operations.
- Avoid unnecessary side effects when a pure transformation would be better represented by Function.
Interview Insight
Interview shortcut: Consumer<T> takes one argument of type T, returns nothing, and uses the accept() method. Its most common role is performing an action on a supplied value.
Quick Revision
| Concept | Key Point |
|---|---|
| Consumer<T> | Accepts one value and performs an action |
| accept() | Executes the Consumer with the supplied value |
| Return type | void |
| forEach() | Commonly accepts a Consumer |
| andThen() | Chains Consumers in execution order |
| Typical use | Printing, logging, updating, processing, notifications |
Final Takeaway
A Consumer represents an action that accepts a value without producing a result. Its accept() method makes it ideal for processing objects, printing information, logging events, and working with collection operations such as forEach(). Once you understand the simple rule "input goes in, action happens, nothing comes back," Consumer becomes one of the easiest and most practical functional interfaces to use in Java.
