Java Streams & Collections
Streams API, immutable collections, Collectors, and idiomatic data manipulation.
# CLAUDE.md — Java Streams & Collections
## Streams
- Use streams for **transformations** (`map`, `filter`, `reduce`, `collect`). Don't use them for side effects (`forEach` mutating external state).
- Stream operations should be **stateless**. The order of evaluation is not guaranteed for parallel streams.
- Don't use parallel streams unless you've measured. The overhead often beats the speedup for typical workloads.
## Collectors
- `toList()` (Java 16+) returns an immutable list. Prefer it over `collect(Collectors.toList())`.
- `toMap(keyFn, valueFn)` throws on duplicate keys — pass a merger if duplicates are possible.
- `groupingBy` for buckets. Combine with downstream collectors:
```java
Map<String, Long> byCategory = items.stream()
.collect(groupingBy(Item::category, counting()));
```
- `partitioningBy(predicate)` is faster than `groupingBy` for binary splits.
## Collections
- Prefer `List.of(...)`, `Map.of(...)`, `Set.of(...)` for small immutable collections.
- `ArrayList` for sequenced mutable lists. `LinkedList` is almost always wrong (cache-hostile).
- `HashMap` for unordered maps. `LinkedHashMap` if you need insertion order. `TreeMap` for sorted.
- `HashSet` / `LinkedHashSet` / `TreeSet` follow the same logic.
- `EnumMap` / `EnumSet` for keys that are enum values — they're array-backed and very fast.
## Iteration
- Enhanced-for is the default: `for (var item : list) { ... }`.
- `IntStream.range(0, n)` for indexed iteration when you actually need the index.
- `forEach` with method references is fine for terminal-only consumption.
- `Iterator` only when you need explicit removal during iteration.
## Immutability
- `Collections.unmodifiableList(list)` wraps a mutable list — the underlying list can still change.
- `List.copyOf(list)` returns a true immutable copy. Prefer it.
- For records, fields are final. For records holding collections, copy them in the canonical constructor:
```java
public record Order(String id, List<Item> items) {
public Order { items = List.copyOf(items); }
}
```
## Optional
- `Optional` as a return type. Never as a field, parameter, or collection element.
- `orElse(default)` evaluates `default` always. Use `orElseGet(() -> ...)` for lazy defaults.
- `map`, `filter`, `flatMap` to chain. Don't `if (opt.isPresent()) { opt.get() }` — that's a missed `ifPresent`.
- `Optional.of(null)` throws. `Optional.ofNullable(value)` doesn't.
## Performance
- Use `Stream.toList()` for results that are read-only. It's faster than `Collectors.toList()`.
- For large streams with many filters, the order matters: `filter` first, `map` later — cheaper.
- Prefer specialized streams (`IntStream`, `LongStream`) for primitives. Boxing kills throughput.
## Don't
- Don't store `null` in collections. It breaks every iteration assumption.
- Don't mutate a collection during iteration via the original reference. Use `Iterator.remove()` or collect-then-replace.
- Don't ignore the return value of `Map.put` if you care about uniqueness — it returns the previous value.
- Don't use `stream().forEach` when an enhanced-for loop says exactly the same thing.
Other Java templates
Modern Java Rules
Java 21+ defaults: records, sealed types, pattern matching, virtual threads, var.
Java Testing with JUnit 5
JUnit 5 + AssertJ + Mockito + Testcontainers — opinionated test conventions.
Java Virtual Threads & Concurrency
Project Loom virtual threads, structured concurrency, and modern concurrent patterns.
Java Build (Maven & Gradle)
Build conventions, dependency management, multi-module projects, and CI patterns.
Spring Framework Core Rules
Dependency injection, bean lifecycle, configuration classes, and profiles.