{"id":51196977,"url":"https://github.com/objectionary/sabj25","last_synced_at":"2026-06-27T21:30:30.109Z","repository":{"id":362713156,"uuid":"1260338737","full_name":"objectionary/sabj25","owner":"objectionary","description":"Full-coverage JMH benchmarks for the Java 25 Stream API, run across six JDK vendors","archived":false,"fork":false,"pushed_at":"2026-06-19T06:22:25.000Z","size":175,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-19T08:23:16.841Z","etag":null,"topics":["benchmark","java","stream","stream-processing"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/objectionary.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-05T11:44:49.000Z","updated_at":"2026-06-19T06:22:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/objectionary/sabj25","commit_stats":null,"previous_names":["objectionary/sabj-25"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/objectionary/sabj25","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objectionary%2Fsabj25","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objectionary%2Fsabj25/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objectionary%2Fsabj25/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objectionary%2Fsabj25/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/objectionary","download_url":"https://codeload.github.com/objectionary/sabj25/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objectionary%2Fsabj25/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34869004,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-27T02:00:06.362Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["benchmark","java","stream","stream-processing"],"created_at":"2026-06-27T21:30:29.393Z","updated_at":"2026-06-27T21:30:30.101Z","avatar_url":"https://github.com/objectionary.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Stream API Benchmarks for Java 25\n\n[![benchmark](https://github.com/objectionary/sabj25/actions/workflows/benchmark.yml/badge.svg)](https://github.com/objectionary/sabj25/actions/workflows/benchmark.yml)\n[![mvn](https://github.com/objectionary/sabj25/actions/workflows/mvn.yml/badge.svg)](https://github.com/objectionary/sabj25/actions/workflows/mvn.yml)\n[![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/objectionary/sabj25/blob/master/LICENSE.txt)\n\nThis repository contains a set of benchmarks for [Stream API] in Java 25.\n\nThe motivation of this repository is the lack of benchmarks that would\n  cover the entire set of terminal and non-terminal methods of\n  Java Stream API, in their different combinations.\nThe benchmarks of [Biboudis et al.][biboudis2014] ([sources][cotl])\n  and [Møller et al.][moller2020] ([sources][streamliner])\n  only cover `map()` and `filter()` methods.\n[Rosales et al.][rosales2023] go further with `StreamProf`, the first\n  dedicated stream profiler for the JVM, yet they target the runtime\n  overhead of `map()`, `filter()`, and `reduce()` rather than the\n  breadth of the API.\n\nA few open-source projects on GitHub benchmark streams as well,\n  yet each of them stays narrow:\n  [keaz/java-stream-benchmark][keaz] measures only `sort()` and\n  `toString()` to tell when a parallel stream pays off,\n  [Pask423/stream-benchmarks][pask423] studies the internals of\n  parallel streams across a range of thread counts,\n  [phonty29/stream-benchmarks][phonty29] compares streams against\n  ordinary `for` loops,\n  and [Nikolas-Charalambidis/java-16-mapmulti-benchmark][mapmulti]\n  contrasts the `mapMulti()` method with `flatMap()`.\nThe [SoftwareMill][softwaremill] blog benchmarks a single\n  log-processing pipeline of `map()`, `filter()`, and\n  `collect(groupingBy())`, again touching only a handful of methods.\nNone of them exercise the full set of terminal and intermediate\n  methods, let alone their combinations.\n\n## Principles\n\nThe pipelines are built to measure the Stream API itself, not the\n  cleverness of a particular JIT compiler. They follow a few rules:\n\n- **No incidental repetition.** Where a pipeline sets out to cover the\n  API, each method appears exactly once, so it measures the operation in\n  combination with others rather than a loop of the same call.\n  Repetition shows up only when it is the subject itself, as in the\n  chain inside `stateless` that repeats `map()` and `filter()` on\n  purpose to turn the call sites megamorphic.\n- **No easy optimization hotspots.** A `Blackhole` observation sits\n  after each boxing stage, because otherwise GraalVM's partial escape\n  analysis scalar-replaces the boxed `Long` and `Double` values and the\n  whole pipeline collapses to almost nothing, measuring elision instead\n  of work.\n- **Lambdas do real arithmetic.** No lambda is an identity function;\n  every element flows through genuine computation at each step.\n- **Every result is verified.** Each pipeline checks its result against a\n  precomputed constant, so a run that silently skipped work fails loudly\n  rather than reporting a fast but wrong number.\n  Order-insensitive pipelines fold into a plain sum, while pipelines\n  whose contract includes encounter order, such as `sorted()`,\n  `limit()`, and `forEachOrdered()`, fold through a rolling `31`-based\n  mix, so a result that holds the right elements in the wrong order\n  fails too.\n- **The full API, in combination.** The pipelines span all four stream\n  types, `long`, `int`, `double`, and object, and weave terminal and\n  intermediate methods together, including `flatMap()` and `mapMulti()`.\n- **One facet of the API per benchmark.** `stateless` covers the\n  stateless operations including the one-to-one scalar conversions,\n  `stateful` the operations that must remember state, `fold` the\n  reduction terminals, `ordered` the short-circuiting ones, and so on\n  through gatherers, collectors, sources, and parallel execution.\n\n## Results\n\nThe numbers come from [JMH][jmh] 1.37, the Java Microbenchmark Harness,\n  driven from a JUnit test that builds the `Options` and runs the `Runner`\n  programmatically rather than from the command line.\nEvery method is measured in `AverageTime` mode and reported in\n  milliseconds per operation (`ms/op`), so each score is the mean wall-clock\n  time of a single invocation of one `@Benchmark` method.\nBecause each pipeline processes a different element count and operation mix,\n  the rows measure distinct workloads: compare JVMs down a single column,\n  not one benchmark against another across rows.\nEach benchmark runs in two forks (each a freshly started JVM), with three\n  warmup iterations of one second each to let the JIT compiler settle,\n  followed by five measurement iterations of two seconds each; the reported\n  score is the arithmetic mean of all ten measurement iterations across the\n  two forks.\nThe state is thread-scoped and JMH drives each benchmark from a single\n  measurement thread, though the pipelines that call `parallel()` still fan\n  their work across the shared fork-join pool; a `Blackhole` consumes\n  intermediate boxed values to stop the JIT from eliminating the pipeline\n  as dead code.\nJMH writes the raw results to `target/jmh-result.csv`, which the CI\n  pipeline parses into the table below.\nThe benchmarks run on every push to `master`, once per JVM, and the\n  table is regenerated automatically:\n\n\u003c!-- benchmark_begin --\u003e\n\n| Benchmark | Temurin 25 | Zulu 25 | Corretto 25 | GraalVM 25 | Oracle 25 | Semeru 25 |\n| --- | ---: | ---: | ---: | ---: | ---: | ---: |\n| `collectors` | 1312.738 | 1221.746 | 1092.833 | 787.697 | 1095.761 | 1169.289 |\n| `concurrent` | 265.958 | 206.017 | 214.983 | 179.688 | 207.396 | 288.608 |\n| `fanout` | 85.001 | 91.858 | 78.938 | 35.033 | 70.783 | 149.798 |\n| `fold` | 142.771 | 163.478 | 174.177 | 129.078 | 175.477 | 446.568 |\n| `gatherers` | 86.445 | 86.965 | 89.281 | 73.876 | 90.003 | 171.074 |\n| `materialize` | 117.761 | 142.358 | 156.501 | 81.877 | 159.956 | 170.113 |\n| `ordered` | 71.211 | 84.394 | 97.637 | 29.163 | 99.031 | 109.319 |\n| `overhead` | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | 0.001 |\n| `sources` | 157.340 | 155.154 | 166.665 | 146.758 | 168.246 | 246.980 |\n| `stateful` | 23.012 | 68.589 | 77.165 | 63.881 | 75.528 | 77.044 |\n| `stateless` | 69.037 | 79.759 | 86.250 | 81.295 | 85.274 | 141.200 |\n| `text` | 8.955 | 9.562 | 10.366 | 10.887 | 10.174 | 15.974 |\n\nAll scores are in milliseconds per operation (ms/op); lower is better.\nThe results were calculated in [this GHA job][benchmark-gha]\non 2026-06-12 at 07:38.\nEach JVM ran on its own GitHub-hosted Linux runner,\nso the scores across columns are indicative, not strictly comparable.\n\u003c!-- benchmark_end --\u003e\n\n[biboudis2014]: https://arxiv.org/abs/1406.6631\n[cotl]: https://github.com/biboudis/clashofthelambdas\n[moller2020]: https://dl.acm.org/doi/abs/10.1145/3428236\n[streamliner]: https://github.com/cs-au-dk/streamliner\n[keaz]: https://github.com/keaz/java-stream-benchmark\n[pask423]: https://github.com/Pask423/stream-benchmarks\n[phonty29]: https://github.com/phonty29/stream-benchmarks\n[mapmulti]: https://github.com/Nikolas-Charalambidis/java-16-mapmulti-benchmark\n[rosales2023]: https://arxiv.org/abs/2302.10006\n[softwaremill]: https://softwaremill.com/benchmarking-java-streams/\n[benchmark-gha]: https://github.com/objectionary/sabj25/actions/runs/27401566362\n[jmh]: https://github.com/openjdk/jmh\n[Stream API]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/stream/package-summary.html\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fobjectionary%2Fsabj25","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fobjectionary%2Fsabj25","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fobjectionary%2Fsabj25/lists"}