filter() keeps elements where a Predicate returns true; it's lazy and doesn't run until a terminal op like collect() or count()
Combine multiple filter() calls or use && for AND, || for OR — but && is more readable for complex conditions
filter(Objects::nonNull) is the standard null-safe filter before downstream ops
Multiple filter() calls internally merge into one operation — no performance penalty from chaining
filter().findFirst() short-circuits; filter().count() does not — understand the difference for streaming large datasets
Biggest mistake: forgetting filter() is lazy — debugging filter() without a terminal op shows no filtering
✦ Definition~90s read
What is Java Stream filter()?
Java Stream's filter() is a lazy intermediate operation that applies a Predicate<T> to each element, retaining only those that return true. It exists to declaratively subset data without explicit loops, enabling functional-style pipelines. However, filter() itself does not guard against nulls — if your stream contains null elements and your predicate calls methods on them (e.g., s -> s.startsWith("A")), you get a NullPointerException at evaluation time.
★
Stream.filter() is the Java streams version of 'keep only the elements that match this condition'.
This is a common trap: filter() only evaluates the predicate, it doesn't sanitize the stream. You must explicitly handle nulls via Objects::nonNull in a preceding filter, or use Optional-based patterns. In practice, chaining stream.filter(Objects::nonNull).filter(...) is the standard null-safe pattern, but beware that ordering matters — placing the null check first avoids NPE in subsequent predicates.
For large datasets, filter() short-circuits on infinite streams only if combined with limit() or findFirst(), and predicate composition via and(), or(), negate() can reduce intermediate stream passes. When you need null-tolerant filtering, consider wrapping predicates with Predicate.isEqual(null).negate() or using Stream.ofNullable() (Java 9+) to avoid nulls at the source.
Plain-English First
Stream.filter() is the Java streams version of 'keep only the elements that match this condition'. You pass a Predicate — a function that returns true or false for each element — and filter() keeps only the true ones. It's lazy: the filtering doesn't execute until a terminal operation (collect, count, findFirst) is called.
filter() is the building block of data processing in modern Java. The power comes not from filter() alone but from composing it with map(), flatMap(), sorted(), and collect() in a readable declarative pipeline that describes what you want, not how to iterate to get it.
How Java Stream filter() Handles Nulls — and When It Doesn't
Stream.filter() applies a Predicate to each element in a stream, retaining only those for which the predicate returns true. It's a stateless intermediate operation — each element is evaluated independently, and the result is a new stream. The core mechanic is straightforward: for every element, evaluate predicate.test(element); if true, include it; if false, skip it.
What matters in practice: filter() does not automatically skip null elements. If your predicate calls a method on the element (e.g., s -> s.startsWith("A")), a null element will throw a NullPointerException at evaluation time. The stream pipeline doesn't guard against nulls — you must either filter them out explicitly (filter(Objects::nonNull)) or handle them inside the predicate. This is O(n) per filter operation, but the real cost is a crashed pipeline if nulls are unexpected.
Use filter() when you need to select a subset of elements based on a condition — validation, access control, data cleaning. In production systems, the most common mistake is assuming the source collection is null-free. Always place a null-check filter before any predicate that dereferences the element. This is especially critical when consuming data from external APIs, databases, or user input where nulls are a fact of life.
Nulls Are Not Filtered Automatically
Stream.filter() does not skip null elements. A null element will reach your predicate and cause a NullPointerException unless you explicitly filter it out first.
Production Insight
A team ingested customer records from a legacy CRM; a single null name field caused a filter( name -> name.startsWith("A") ) to throw NPE, aborting the entire batch job.
Symptom: a NullPointerException with no stack trace pointing to the null element — just the filter line, making debugging slow.
Rule: always apply filter(Objects::nonNull) as the first operation when the source can contain nulls, or use a null-safe predicate.
Key Takeaway
Stream.filter() does not skip nulls — it evaluates every element, including null.
Always filter out nulls explicitly before any predicate that dereferences the element.
A single null in the stream will crash the entire pipeline if not handled.
thecodeforge.io
Java Stream Filter
filter() Basics — The Predicate and Lazy Execution
Stream.filter() takes a Predicate<T> — a functional interface with a single test(T) method returning boolean. Every element is tested; only those where test returns true survive. filter() is an intermediate operation: it doesn't execute until a terminal operation like collect(), forEach(), or findFirst() is called.
This means you can chain multiple intermediate ops (filter, map, sorted) and the whole pipeline runs in one pass when the terminal op is invoked. The JVM may also fuse adjacent filter() calls into a single predicate internally for performance.
FilterBasics.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package io.thecodeforge.streams;
import java.util.List;
import java.util.stream.Collectors;
publicclassFilterBasics {
publicstaticvoidmain(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
// filter() + collect() triggers executionList<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evens); // [2, 4, 6]// Without terminal op — nothing prints
numbers.stream()
.filter(n -> {
System.out.println("Testing " + n);
return n % 2 == 0;
});
// No output because filter() is lazy
}
}
Output
[2, 4, 6]
(No output from second stream)
Lazy Evaluation Trap
A common debugging mistake: adding System.out.println inside filter() and not seeing output. Remember, nothing executes until a terminal operation is called.
Production Insight
Lazy evaluation means filter() alone never throws — only when a terminal op triggers the pipeline.
In production, a pipeline that never terminates silently does no filtering, wasting CPU cycles on constructing the stream object without performing work.
Always add a terminal call, even if you discard the result (e.g., .count() or .allMatch(...)) to ensure the stream runs.
Key Takeaway
filter() defines what to keep; a terminal operation decides when to keep it.
Without a terminal operation, filter() does nothing.
Always terminate your stream.
Multiple Conditions: Chaining vs. Single Predicate
You can filter on multiple conditions either by chaining multiple filter() calls or by combining conditions with && inside one filter(). Both produce the same result, but readability and performance differ slightly.
Multiple filter() calls are often more readable — each expresses a single concern. The stream library is smart enough to fuse them into a single predicate internally. However, if one condition is much more selective than others, ordering matters: filter out the rarest condition first to reduce elements flowing through subsequent filters.
Multiple filter() calls vs. a single && predicate — performance is nearly identical because the JIT compiler can inline both.
However, ordering matters: if one condition is cheap and highly selective (e.g., filtering out 90% of elements), put it first. This reduces the number of elements the more expensive condition must evaluate.
In production code, prefer multiple filter() calls when conditions are conceptually separate; it improves readability and makes unit testing each condition easier.
Key Takeaway
Multiple filter() calls are fused by the JVM; readability wins.
Place the most selective condition first to minimise work.
Don't micro-optimise without profiling.
When to Chain vs Combine
IfConditions are independent and conceptually distinct
→
UseUse multiple filter() calls for readability and easier debugging
IfConditions are closely related and always evaluated together
→
UseUse a single filter() with && for conciseness
IfPerformance-critical path with millions of elements
→
UseProfile both approaches; ordering matters more than style
thecodeforge.io
Java Stream Filter
Predicate Composition: and(), or(), negate()
Java 8 introduced Predicate methods for composition: and(), or(), and negate(). These let you build complex filters without deep nesting of && and ||, especially when predicates are stored as variables or reused.
Use Predicate.not() (Java 11+) for negation. Combine predicates with .and() and .or() for more expressive pipeline construction.
PredicateComposition.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package io.thecodeforge.streams;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
publicclassPredicateComposition {
publicstaticvoidmain(String[] args) {
List<String> words = List.of("cat", "dog", "elephant", "ant", "");
Predicate<String> notEmpty = s -> !s.isEmpty();
Predicate<String> hasVowel = s -> s.matches(".*[aeiou].*");
Predicate<String> longerThan3 = s -> s.length() > 3;
// Use .and() to combineList<String> result = words.stream()
.filter(notEmpty.and(hasVowel).and(longerThan3))
.collect(Collectors.toList());
System.out.println(result); // [elephant]// Use .or() with negatePredicate<String> noVowel = Predicate.not(hasVowel);
List<String> noVowels = words.stream()
.filter(notEmpty.and(noVowel))
.collect(Collectors.toList());
System.out.println(noVowels); // [] because all words have vowels
}
}
Output
[elephant]
[]
Production Insight
Predicate composition is useful when the same filter condition appears in multiple places — extract it to a named Predicate constant and compose. However, be cautious with .and() and .or() in parallel streams: if the predicates share mutable state, you'll get race conditions. Always keep predicates stateless.
In production, heavy predicate composition with many .and() calls can obscure the logic. A helper method returning a Predicate can improve readability.
Key Takeaway
Use Predicate.and(), .or(), and .negate() to compose filters.
Extract reusable predicates for DRY code.
Keep predicates stateless for parallel safety.
Null-Safe Filtering and Optional Handling
Null values in a stream source are a common source of NPE in filter() predicates. The safest pattern is to filter out nulls with filter(Objects::nonNull) immediately after the stream creation. However, if you need to keep nulls for some reason (rare), use inline null checks in the predicate: p -> p != null && condition.
For scenarios where you have a stream of Optionals (e.g., map() returning Optional), use flatMap(Optional::stream) (Java 9+) to unwrap and filter out empties in one step.
filter(Objects::nonNull) must come before any filter that calls methods on the element. If you swap the order, the method-calling predicate throws NPE on nulls.
Production Insight
In production, trusting that data sources never produce null is dangerous. A null can slip in from deserialization of malformed JSON, a database migration, or a bug in a previous step. Adding filter(Objects::nonNull) at the top of every pipeline that performs method calls is a defensive pattern that costs almost nothing — microseconds per million elements — and prevents midnight NPE alerts.
Key Takeaway
filter(Objects::nonNull) first, then call methods.
Defensive null filtering is a one-liner that prevents production outages.
Use Optional::stream with flatMap for filtering alongside mapping.
Performance: Short-Circuiting, Ordering, and Large Datasets
filter() performance depends on the predicate cost, the number of elements, and whether you use short-circuiting operations like findFirst(), anyMatch(), or limit(). These terminate early as soon as a match is found, potentially processing only a fraction of the stream.
For large datasets (millions of elements), the cost of the predicate dominates. Putting an inexpensive, selective predicate first can drastically reduce the number of elements evaluated by downstream filters. Use parallelStream() only if the predicate is stateless, the stream source is large, and the operation is CPU-intensive — otherwise parallel overhead outweighs the benefit.
ShortCircuitPerformance.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package io.thecodeforge.streams;
import java.util.stream.IntStream;
publicclassShortCircuitPerformance {
publicstaticvoidmain(String[] args) {
// findFirst short-circuits — may only inspect first few elementsint firstEven = IntStream.range(1, 10_000_000)
.filter(n -> {
System.out.println("Checking " + n);
return n % 2 == 0;
})
.findFirst()
.orElse(-1);
System.out.println("First even: " + firstEven);
// prints only "Checking 1" then "Checking 2" and stops// count() must process all elements — no short-circuitlong count = IntStream.range(1, 10_000_000)
.filter(n -> n % 2 == 0)
.count();
System.out.println("Count: " + count); // processes all 10M
}
}
Output
Checking 1
Checking 2
First even: 2
Count: 5000000
Production Insight
Short-circuiting can dramatically reduce latency. For example, checking if any order is overdue for an alert: findFirst() stops after the first match, while filter().count() > 0 processes all orders. Use anyMatch() instead of filter().findFirst().isPresent() — it's more idiomatic and signals intent.
For batch processing of large datasets, avoid short-circuiting if you need to process everything. Instead, consider partitioning the stream with limit() and skip() for pagination, but be aware that skip() on unordered streams may not be deterministic — use order-dependent pipelines with caution.
Key Takeaway
Short-circuit ops (findFirst, anyMatch, limit) stop early — great for existence checks.
Expensive predicates first? Optimise ordering via profiling.
parallelStream() helps only if predicates are stateless and work outweighs overhead.
Filtering with Checked Exceptions — The Silent Killer
Your lambda predicate can't throw checked exceptions. That's not a design flaw, it's a feature. But try telling that to a junior who needs to call Files.lines() inside a filter.
The compiler screams. So they wrap it in a try-catch that swallows the exception. Now you have silent failures in your pipeline. Customers disappear from reports. Transactions go missing at 3 AM.
There are two production-viable solutions. First: wrap the checked exception in a runtime wrapper. Second: extract the logic into a method that handles the exception explicitly, returning a boolean. The second is cleaner. The first works when you're refactoring legacy code that's already a dumpster fire.
Don't use ThrowingFunction libraries from some dude's GitHub unless you audit the bytecode. Seen it break on Java 17+ because of module-access issues. Keep it simple. Write a private method. Test it. Move on.
Swallowing IOExceptions in a filter predicate is how you get "mystery missing records" paged at 2 AM. Always log the failure path. Always.
Key Takeaway
Extract exception-prone filter logic into a method that handles checked exceptions cleanly — never let a checked exception die silently inside a lambda.
filter() Before map() — Not Just a Style Preference
A stream pipeline is a contract between cost and cardinality. Every filter() reduces the number of elements flowing downstream. Every map() transforms them. Order matters more than most devs realise.
Put filter() before map(). Always. Unless you have a psychotic reason not to.
Why? Because mapping is expensive. Transforming a million strings into parsed JSON objects, only to discard 70% of them in the next filter, is CPU theft. You're paying for object allocation, garbage collection, and probably some O(n) regex that someone thought was clever.
Benchmarks from a production system processing 5M records/day showed a 40% reduction in GC pressure just by swapping map().filter() to filter().map(). That's not micro-optimisation. That's architecture.
The only exception is when the filter predicate itself requires the mapped value. In that case, you're screwed either way — but you can mitigate it by using .mapMulti() in Java 16+ or hoisting the predicate logic.
FilterBeforeMap.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// io.thecodeforge — java tutorialimport java.util.List;
import java.util.stream.*;
publicclassFilterBeforeMap {
record Invoice(String id, double amount) {}
// Expensive parse simulationprivatestaticInvoiceparse(String line) {
String[] parts = line.split(",");
returnnewInvoice(parts[0], Double.parseDouble(parts[1]));
}
publicstaticvoidmain(String[] args) {
List<String> rawLines = List.of(
"INV-001,1500",
"INV-002,200", // < $1000, should be filtered"INV-003,3200",
"INV-004,50" // < $1000, should be filtered
);
// WRONG: parse all, then filterList<Invoice> wrong = rawLines.stream()
.map(FilterBeforeMap::parse) // parses all 4
.filter(inv -> inv.amount > 1000)
.collect(Collectors.toList());
System.out.println("Wrong: " + wrong);
// CORRECT: filter first, then parse only what passesList<Invoice> right = rawLines.stream()
.filter(line -> {
String[] parts = line.split(",");
returnDouble.parseDouble(parts[1]) > 1000;
})
.map(FilterBeforeMap::parse) // parses only 2
.collect(Collectors.toList());
System.out.println("Right: " + right);
}
}
If you absolutely must map before filter because the predicate needs the transformed data, use .mapMulti() (Java 16+) to flatten your logic into a single pass.
Key Takeaway
Filter first, map later — unless the predicate needs the mapped value. Saves CPU, memory, and your SRE's sanity.
Using Stream.filter()
Before mastering filter(), understand its contract. Stream.filter() applies a Predicate to each element in the stream pipeline and returns a new stream containing only elements where the predicate returns true. The operation is intermediate and lazy — it does not execute until a terminal operation like collect(), forEach(), or count() is called. Filtering preserves the original stream order unless you use parallel streams with unordered sources. The predicate must be stateless and non-interfering (should not modify the stream source). A common mistake is assuming filter() modifies the underlying collection — it does not. You must collect the result. Filtering before map() reduces processing overhead, especially with expensive transformations. The filter() method throws NullPointerException if the predicate is null. Always validate input streams for null to avoid runtime surprises.
Filtering on null-containing streams without explicit null checks causes NullPointerException at runtime. Always handle nulls before accessing fields or methods inside the predicate.
Key Takeaway
filter() is lazy and stateless — always collect results and guard against nulls in the predicate.
Program Steps for Stream.filter()
Executing a filter operation follows a predictable sequence. Step 1: Obtain a stream from a data source (list, set, array, or generator). Step 2: Apply one or more filter() calls with appropriately typed Predicates. Chain them for readability or compose predicates with and(), or(), negate() for performance. Step 3: Optionally add map() or other intermediate operations to transform filtered elements. Step 4: Call a terminal operation to trigger stream evaluation. The entire pipeline runs element-by-element, not step-by-step — this is lazy evaluation. Step 5: Handle the result appropriately — collect into a collection, reduce to a single value, or iterate. Step 6: Anticipate and handle exceptions inside predicates, particularly checked exceptions that require wrapping in unchecked exceptions. Step 7: Validate that terminal operations are present; forgetting them is the most common bug — the stream silently does nothing.
Forgetting the terminal operation (step 4) means the pipeline never runs — check with static analysis or IDE warnings to catch this.
Key Takeaway
All stream pipelines follow: source → intermediate ops → terminal op. Missing the terminal step wastes memory and yields zero output.
● Production incidentPOST-MORTEMseverity: high
NullPointerException in filter() predicate because upstream elements contained nulls
Symptom
Stack trace in production logs pointing to a lambda inside .filter(p -> p.getStatus() == Status.COMPLETED). The stream source List<Payment> contained a null entry.
Assumption
The team assumed the list would never contain null entries because the database column was NOT NULL. But a data migration created a new row with only the ID populated; the object was partially constructed and null slipped in.
Root cause
The stream pipeline didn't filter nulls before calling methods on elements. filter() passes every element to the predicate, including nulls. If the predicate invokes a method on the element, NPE occurs.
Fix
Insert filter(Objects::nonNull) before any filter() that accesses element methods. Also, enforce non-null contract at the data layer and validate object construction.
Key lesson
Always assume a stream source can contain nulls until proven otherwise — filter(Objects::nonNull) at the start of the pipeline is cheap insurance.
Even if the database column is NOT NULL, object mapping or deserialization can introduce nulls. Validate at the earliest point.
Production debug guideSymptom → Action — what to check when filter() isn't working as expected4 entries
Symptom · 01
filter() appears to do nothing — stream returns all elements
→
Fix
Check that a terminal operation (collect, count, findFirst) is called. Without it, filter() never executes. Add a .count() or .collect() at the end.
Symptom · 02
Predicate throws NullPointerException
→
Fix
Inspect the stream source for null elements. Insert filter(Objects::nonNull) before the predicate that accesses methods. Use peek() or debug logging to see the elements.
Symptom · 03
filter() returns fewer elements than expected
→
Fix
Check if conditions are too strict. Test the predicate manually on sample input. Use method references or Predicate.negate() to verify logic.
Symptom · 04
filter() with parallel stream produces inconsistent results
→
Fix
Ensure the predicate is stateless and non-interfering. Side effects (modifying external variables) break parallelism. Convert to sequential if uncertain.
★ Quick Debugging filter()Commands and checks when filter() behaves unexpectedly
No terminal operation -> nothing happens−
Immediate action
Add .collect(Collectors.toList()) or .count() to force execution
filter() keeps only elements where the predicate returns true
it's lazy and doesn't execute until a terminal operation like collect() or count() is called.
2
Chain multiple filter() calls or combine conditions with && and ||
multiple filter() calls are more readable for complex conditions.
3
filter(Objects::nonNull) is the standard idiom for removing nulls from a stream before further processing.
4
For boolean checks (does any element match? do all elements match?), use anyMatch(), allMatch(), or noneMatch() instead of filter().count() > 0.
5
Short-circuiting operations (findFirst, limit) can dramatically reduce processing for large datasets when you only need a subset.
6
Predicate composition via and(), or(), negate() promotes reuse
but keep predicates stateless for parallel safety.
Common mistakes to avoid
5 patterns
×
Calling filter() on a stream with potential null elements without filtering nulls first
Symptom
NullPointerException inside filter() predicate when trying to access methods on null elements — production incidents at 3 AM.
Fix
Always add filter(Objects::nonNull) as the first operation after stream() if the source might contain nulls. Alternatively, use inline null check: p -> p != null && condition.
×
Forgetting filter() is lazy — debugging filter() without a terminal operation
Symptom
System.out.println inside filter() produces no output; developer thinks the condition is wrong, but actually the pipeline never executed.
Fix
Add a terminal operation like collect(), forEach(), or count() to trigger execution. Use .peek() for debugging, which is also lazy until terminal op.
×
Mutating external state inside filter() predicate
Symptom
Inconsistent results when switching to parallelStream() — shared mutable state causes race conditions, output varies per run.
Fix
Keep filter() predicates stateless and non-interfering. If you need to collect additional information, use collect() with a custom collector instead of side-effecting in filter().
×
Using filter().findFirst() when anyMatch() suffices
Symptom
The code filter().findFirst().isPresent() works but is verbose and creates an Optional overhead. Also, findFirst() returns the first element even if you only care about existence.
Fix
Use anyMatch(predicate) directly — it's cleaner and short-circuits as soon as it finds a match, without needing to extract an element.
×
Assuming filter() on parallel stream is faster without checking predicate cost
Symptom
Parallel stream performs worse than sequential on small datasets or when predicate is cheap (e.g., simple integer comparison).
Fix
Only use parallelStream() when the dataset is large (tens of thousands+) and the predicate is CPU-intensive (e.g., complex regex, database call). For most cases, sequential is fine.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01JUNIOR
What is the difference between Stream.filter() and Stream.anyMatch()?
Q02JUNIOR
How would you filter a List to only include completed payments ...
Q03SENIOR
Is Stream.filter() eager or lazy? What are the implications?
Q04SENIOR
How does multiple filter() calls affect performance compared to a single...
Q05SENIOR
Explain a production incident where filter() caused a failure and how yo...
Q01 of 05JUNIOR
What is the difference between Stream.filter() and Stream.anyMatch()?
ANSWER
filter() is an intermediate operation that returns a new stream containing only elements that satisfy the predicate. It's lazy and requires a terminal operation. anyMatch() is a short-circuiting terminal operation that returns a boolean indicating whether any element matches the predicate. Use filter() when you want a filtered collection; use anyMatch() when you only need to know if at least one element matches.
Q02 of 05JUNIOR
How would you filter a List to only include completed payments over £100 using Java streams?
ANSWER
Use stream().filter() with two conditions: .filter(p -> "completed".equalsIgnoreCase(p.status()) && p.amount() > 100).collect(Collectors.toList()). Alternatively, chain two filter() calls for readability: .filter(p -> "completed".equalsIgnoreCase(p.status())).filter(p -> p.amount() > 100).
Q03 of 05SENIOR
Is Stream.filter() eager or lazy? What are the implications?
ANSWER
filter() is lazy — it doesn't perform any filtering until a terminal operation (like collect or forEach) is invoked on the stream. Implications: (1) You can build a pipeline of multiple intermediate operations without processing elements until needed. (2) If you forget a terminal operation, no filtering happens. (3) Short-circuiting operations like findFirst() can stop early, only processing elements until a match is found. (4) Intermediate operations can be fused for efficiency.
Q04 of 05SENIOR
How does multiple filter() calls affect performance compared to a single filter with &&?
ANSWER
The JVM can fuse multiple filter() calls into a single predicate through JIT inlining, so performance is virtually identical in most cases. However, ordering matters: if one filter is much more selective (reduces 90% of elements), put it first to reduce the workload of subsequent filters. In micro-benchmarks, the difference is negligible; readability should guide your choice.
Q05 of 05SENIOR
Explain a production incident where filter() caused a failure and how you fixed it.
ANSWER
We had a NullPointerException in a filter() predicate because the stream source contained null elements (from a partial data migration). The fix was to add filter(Objects::nonNull) before the method-calling filter. The lesson: always guard against nulls at the start of a stream pipeline, even if you think the source is clean.
01
What is the difference between Stream.filter() and Stream.anyMatch()?
JUNIOR
02
How would you filter a List to only include completed payments over £100 using Java streams?
JUNIOR
03
Is Stream.filter() eager or lazy? What are the implications?
SENIOR
04
How does multiple filter() calls affect performance compared to a single filter with &&?
SENIOR
05
Explain a production incident where filter() caused a failure and how you fixed it.
SENIOR
FAQ · 5 QUESTIONS
Frequently Asked Questions
01
How do I filter a list in Java 8+?
Use Stream.filter(): list.stream().filter(element -> condition).collect(Collectors.toList()). The filter() method takes a Predicate (a function returning boolean) and keeps only elements where the predicate returns true.
Was this helpful?
02
How do I filter null values from a Java stream?
Use filter(Objects::nonNull): stream.filter(Objects::nonNull).collect(Collectors.toList()). This removes all null elements before downstream operations, preventing NullPointerException in subsequent map() or other operations.
Was this helpful?
03
Can I combine multiple filter conditions in one stream?
Yes, you can either chain multiple .filter() calls or use && inside a single predicate: .filter(p -> condition1 && condition2). Both work; use multiple filters when conditions are logically separate for better readability.
Was this helpful?
04
What's the difference between filter().findFirst() and anyMatch()?
filter().findFirst() returns an Optional containing the first element matching the condition. anyMatch() returns a boolean — it's more concise and signals the intent of 'does any element match?' without extracting an element. Use anyMatch() for existence checks.
Was this helpful?
05
Does filter() support parallel streams?
Yes, filter() works with parallel streams. However, the predicate must be stateless and non-interfering. Also, parallel performance gains are only realised with large datasets and expensive predicates. Always measure before committing to parallel.