Two classifiers stacked - the minute, then the outcome - give a per-window breakdown in one pass. The rate is computed from the inner map, so a minute with no errors still reports 0% rather than being missing.
record Req(int atSeconds, int status) {}
var reqs = List.of(new Req(5, 200), new Req(50, 200), new Req(65, 500), new Req(70, 200),
new Req(130, 200), new Req(131, 500), new Req(190, 500));
Map<Integer, Map<String, Long>> perMinute = reqs.stream()
.collect(Collectors.groupingBy(r -> r.atSeconds() / 60, TreeMap::new,
Collectors.groupingBy(r -> r.status() >= 500 ? "err" : "ok",
TreeMap::new, Collectors.counting())));
perMinute.forEach((minute, counts) -> {
long total = counts.values().stream().mapToLong(Long::longValue).sum();
System.out.println("minute " + minute + ": " + counts + " errorRate="
+ String.format(Locale.ROOT, "%.0f%%", 100.0 * counts.getOrDefault("err", 0L) / total));
});
minute 0: {ok=2} errorRate=0%
minute 1: {err=1, ok=1} errorRate=50%
minute 2: {err=1, ok=1} errorRate=50%
minute 3: {err=1} errorRate=100%
Run this yourself in the Online Java Compiler, spin up a live REST API in the API Sandbox, or practise with Java interview questions.
Published 2026-09-20