Java 8 features
Java 8 streams: lifetime, duplicate keys and safe collection
By the BackendDrills editorial team · Published and checked October 6, 2026 · 7-minute read
Before you start: Basic Java collections and lambdas. Find this reading in a study path →
A stream describes a computation over a source. Treat its lifetime and the collector's meaning as part of the API contract. These examples use Java 8 APIs; they do not rely on later additions such as Stream.toList().
A report with two questions
A small usage report needs the number of failed jobs and their IDs. A developer stores a filtered stream, counts it, then tries to collect it. The count consumes that pipeline. Create a fresh pipeline from a stable collection for each query, or deliberately collect a reusable snapshot first. If the source is a live database query, two executions can observe different data; decide which consistency contract the report needs.
// Illustrative Java 8 fragment: jobs is an in-memory List.
long failed = jobs.stream().filter(Job::isFailed).count();
List<String> ids = jobs.stream().filter(Job::isFailed)
.map(Job::getId).collect(Collectors.toList());
Duplicate keys are a business decision
Now suppose an import maps customer ID to email. Two source rows contain the same ID with different addresses. The two-argument toMap collector rejects duplicate keys. Adding an arbitrary “keep the first” merge makes the exception disappear while leaving the meaning undefined. Decide whether duplicates indicate invalid input, multiple values, or competing versions. If choosing the latest record, establish which timestamp or sequence is authoritative and how ties behave.
Build a fixture with a duplicate ID, equal timestamps and reversed input order. Write the expected result before implementing the merge. A collector that silently depends on accidental input order should fail this review when the contract promises a version-based result. If all emails are meaningful, grouping values may express the requirement more honestly than selecting one.
A small experiment
- Use five jobs: two failed, two successful and one with deliberately invalid input.
- Predict which operation evaluates each predicate and which operation consumes the pipeline.
- Compare two fresh traversals with collecting a snapshot once. Record the extra memory and source-consistency trade-off.
- Test your duplicate policy with reordered rows. Keep effects out of pipeline callbacks so tests can inspect a returned value.
Before adding parallelism
Identify independent work, a valid reduction and the limiting resource. More threads do not grant more database connections or make shared mutations safe. Keep a sequential baseline and compare correctness before timing. Finish by explaining one input on which your original collector would produce an unacceptable result.
Check the underlying behavior
Original illustrative examples, prepared with AI assistance and checked against the linked primary documentation. No customer incident or vendor endorsement is claimed. Our editorial approach.
Practice the next decision
Try a complete free backend drill: inspect evidence, make three decisions and review the reasoning. No account or card required.
Try a free incident drill →Selected practice from the study paths
- Decimal money changes after a round trip · Full edition drill
- Stream reuse fails on the second query · Full edition drill
- Duplicate keys abort a collector · Full edition drill
- thenApply blocks the completion thread · Full edition drill
Free readings need no account. Full edition drills require verified access; opening a paid link does not expose its answers.