Question
What is the Java equivalent of LINQ for querying, filtering, transforming, and collecting data from collections?
Short Answer
Java does not include LINQ with the same syntax as C#, but the Java Stream API is the standard built-in approach for performing LINQ-like operations on collections. You will learn how to use streams with operations such as filter, map, sorted, and collect, and how they differ from ordinary loops.
Concept
The closest standard Java equivalent to C# LINQ is the Stream API, introduced in Java 8.
A stream represents a pipeline for processing a sequence of values. It lets you describe what result you want rather than manually controlling indexes, temporary lists, and loop flow.
For example, suppose you need the names of active users, sorted alphabetically. With a stream, you can:
- Start with a collection.
- Filter out inactive users.
- Transform each remaining user into a name.
- Sort the names.
- Collect the result into a list.
List<String> activeNames = users.stream()
.filter(User::isActive)
.map(User::getName)
.sorted()
.collect(Collectors.toList());
This is similar in purpose to a LINQ query such as Where, Select, OrderBy, and ToList.
Streams matter because collection processing is everywhere in real programs: API results, database records already loaded into memory, file lines, user input, reports, and event data. A stream pipeline can make this processing concise and easier to read when used appropriately.
A crucial distinction: Java streams process data in memory. They are not automatically database queries. Database query tools may offer LINQ-like APIs, but those are separate libraries or frameworks.
Mental Model
Think of a stream as an assembly line.
- The source collection is a box of raw items arriving at the line.
filteris an inspector who removes items that do not meet a rule.mapis a worker who converts each item into a different form.sortedarranges the items.collectpacks the final items into a new box, such as aList.
Each stage describes one transformation. The original collection stays unchanged unless you explicitly modify it elsewhere.
Syntax and Examples
A stream pipeline usually has three parts:
source.stream()
.intermediateOperation()
.terminalOperation();
- Source: usually a
Collection, such as aListorSet. - Intermediate operations: build the pipeline, for example
filter,map,sorted, anddistinct. - Terminal operation: produces a result or performs work, for example
collect,count,findFirst,forEach, orreduce.
Filter numbers
import java.util.List;
import java.util.stream.Collectors;
List<Integer> scores = List.of(45, 72, 90, 58, 81);
List<Integer> passingScores = scores.stream()
.filter(score -> score >= 60)
.collect(Collectors.toList());
System.out.println(passingScores);
Step by Step Execution
Consider this pipeline:
import java.util.List;
import java.util.stream.Collectors;
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
List<Integer> result = numbers.stream()
.filter(number -> number % 2 == 0)
.map(number -> number * 10)
.collect(Collectors.toList());
System.out.println(result); // [20, 40]
Execution is easiest to understand item by item:
numbers.stream()creates a stream over1, 2, 3, 4, 5.filter(number -> number % 2 == 0)keeps only even values:1is removed.2continues.3is removed.4continues.5is removed.
map(number -> number * 10)transforms the remaining values:2becomes20.
Real World Use Cases
Streams are useful when you need to process a group of in-memory values.
- API response processing: keep orders with a
PAIDstatus and return their order IDs. - Reporting: total the prices of items in a shopping cart.
- Search: find products whose names contain a search term.
- Data cleanup: trim text, remove blank entries, and eliminate duplicates.
- Permissions: select actions allowed for the current user.
- Import validation: identify invalid rows before saving valid rows.
Example: calculate the total cost of available products.
int total = products.stream()
.filter(Product::isAvailable)
.mapToInt(Product::getPriceInCents)
.sum();
mapToInt creates an IntStream, which provides numeric operations such as sum, average, min, and max.
Real Codebase Usage
In real Java projects, streams are commonly used for small, focused collection transformations.
Validate before processing
Use a guard clause before creating a pipeline when an input may be invalid.
public List<String> normalizeTags(List<String> tags) {
if (tags == null) {
return List.of();
}
return tags.stream()
.filter(tag -> tag != null && !tag.isBlank())
.map(String::trim)
.map(String::toLowerCase)
.distinct()
.sorted()
.collect(Collectors.toList());
}
Build lookup maps
A common task is turning a list into a map keyed by an ID.
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
Map<Long, User> usersById = users.stream()
.collect(Collectors.toMap(User::getId, Function.identity()));
This assumes IDs are unique. If duplicate keys are possible, provide a merge rule.
Map<Long, User> usersById = users.stream()
.collect(Collectors.toMap(
User::getId,
Function.identity(),
(first, second) -> first
));
Find one value safely
Use Optional-returning operations instead of assuming an item exists.
Common Mistakes
Forgetting a terminal operation
This pipeline does not produce a list because filter only creates another stream.
List<Integer> values = List.of(1, 2, 3);
values.stream().filter(value -> value > 1); // Result is ignored
Finish with a terminal operation:
List<Integer> result = values.stream()
.filter(value -> value > 1)
.collect(Collectors.toList());
Reusing a stream
A stream is consumed after a terminal operation.
Stream<String> stream = List.of("a", "b").stream();
long count = stream.count();
stream.forEach(System.out::println); // Throws IllegalStateException
Create a new stream from the collection when needed.
Expecting map to remove items
map transforms every item; it does not select items.
List<String> names = List.of("Ada", "", );
List<String> result = names.stream()
.map(String::trim)
.collect(Collectors.toList());
Comparisons
| C# LINQ idea | Java Stream API equivalent | Purpose |
|---|---|---|
Where(...) | filter(...) | Keep matching values |
Select(...) | map(...) | Transform each value |
OrderBy(...) | sorted(...) | Sort values |
ToList() | collect(Collectors.toList()) | Create a list |
Count() | count() | Count values |
Cheat Sheet
// Start a stream from a collection
items.stream()
// Keep matching items
.filter(item -> condition)
// Transform each item
.map(item -> transformedItem)
// Remove duplicates and sort
.distinct()
.sorted()
// Create a List
.collect(Collectors.toList())
// Count matches
.filter(item -> condition)
.count()
// Check for matches
.anyMatch(item -> condition)
// Find one item
.findFirst() // returns Optional<T>
// Sum integer values
.mapToInt(Item::getQuantity)
.sum()
Key rules:
- A stream pipeline needs a terminal operation such as
collect,count,sum, orforEachto run. filterselects values;maptransforms values.- Streams generally do not alter the original collection.
- A stream can be consumed only once.
- Use
Collectors.toList()for broad compatibility across Java versions.
FAQ
Is there an exact LINQ equivalent in Java?
No. Java does not have LINQ query syntax built into the language. The standard Stream API provides many equivalent operations for in-memory collection processing.
What Java version introduced streams?
The Stream API was introduced in Java 8.
Is stream() the same as a collection?
No. A collection stores values. A stream processes values from a source and is normally consumed once.
Does filter change the original list?
No. filter creates a processing stage. When collected, it usually creates a separate result collection.
When should I use a loop instead of a stream?
Use a loop when the logic needs complex branching, early exits across multiple steps, detailed debugging, or controlled mutation. Use a stream for clear transformations and queries over collections.
Can Java streams query a database?
Not by themselves. Streams process in-memory data. Database frameworks may offer query APIs, but those execute through the database layer rather than through ordinary Java streams.
Why does findFirst() return Optional?
The stream may be empty. Optional makes the possible absence of a result explicit.
Should I use parallelStream() for better performance?
Only after measuring. Parallel processing can be slower for small workloads and may create problems when the pipeline performs shared mutable operations.
Mini Project
Description
Create a small order-reporting feature. An online shop has a list of orders, and the application needs a report containing the names of paid orders worth at least a chosen amount. This demonstrates a realistic stream pipeline that filters objects, transforms them, sorts results, and collects them into a list.
Goal
Produce an alphabetically sorted list of customer names for qualifying paid orders.
Requirements
Requirement 1
Keep learning
Related questions
Add External JAR Files to an IntelliJ IDEA Java Project
Learn how to add external JAR dependencies to an IntelliJ IDEA Java project using module libraries, and when to use Maven or Gradle instead.
Avoiding Java Code in JSP with JSP 2: EL and JSTL Explained
Learn how to avoid Java scriptlets in JSP 2 using Expression Language and JSTL, with examples, best practices, and common mistakes.
Call a Method After a Delay in Android Java
Learn how to run Java code after a delay in Android using Handler.postDelayed, manage the main thread, and cancel callbacks safely.