{"id":51533778,"url":"https://github.com/martinfrancois/java-streams-skill","last_synced_at":"2026-07-09T06:02:41.462Z","repository":{"id":366832326,"uuid":"1257776317","full_name":"martinfrancois/java-streams-skill","owner":"martinfrancois","description":"Help AI coding agents use Java Streams and Collectors well in new code, review, and cleanup without replacing one antipattern with another.","archived":false,"fork":false,"pushed_at":"2026-06-23T13:37:27.000Z","size":479,"stargazers_count":0,"open_issues_count":4,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-23T14:27:15.261Z","etag":null,"topics":["ai-coding","ai-tools","best-practices","clean-code","code-quality","code-review","coding-agent","collectors","developer-tools","functional-programming","java","java-collectors","java-streams","llm","llm-agents","openai-codex","refactoring","skills","streams","tessl"],"latest_commit_sha":null,"homepage":"https://tessl.io/registry/martinfrancois/java-streams","language":"Python","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/martinfrancois.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-03T02:06:30.000Z","updated_at":"2026-06-22T20:47:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/martinfrancois/java-streams-skill","commit_stats":null,"previous_names":["martinfrancois/java-streams-skill"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/martinfrancois/java-streams-skill","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martinfrancois%2Fjava-streams-skill","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martinfrancois%2Fjava-streams-skill/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martinfrancois%2Fjava-streams-skill/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martinfrancois%2Fjava-streams-skill/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/martinfrancois","download_url":"https://codeload.github.com/martinfrancois/java-streams-skill/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martinfrancois%2Fjava-streams-skill/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35288810,"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-07-09T02:00:07.329Z","response_time":57,"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":["ai-coding","ai-tools","best-practices","clean-code","code-quality","code-review","coding-agent","collectors","developer-tools","functional-programming","java","java-collectors","java-streams","llm","llm-agents","openai-codex","refactoring","skills","streams","tessl"],"created_at":"2026-07-09T06:02:40.599Z","updated_at":"2026-07-09T06:02:41.450Z","avatar_url":"https://github.com/martinfrancois.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Java Streams Skill for AI Agents\n\n[![tessl](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.tessl.io%2Fv1%2Fbadges%2Fmartinfrancois%2Fjava-streams)](https://tessl.io/registry/martinfrancois/java-streams)\n\nAI agents often know Java streams well enough to chain `filter`, `map`, and `collect`, but not\nenough to choose the right stream operation for the job in new code, reviews, and cleanup.\n\nThey write code that looks modern at first glance, then materializes a list just to check whether\nanything matched, sorts a whole stream to get one newest item, counts for existence, uses boxed\nnumeric reductions, changes `findFirst()` to `findAny()` without noticing the order contract, or\nadds `parallelStream()` where it makes the code slower or less predictable.\n\nThis skill gives the agent a compact decision guide before it writes or changes stream code: choose\nthe stream terminal operation that matches the result, preserve ordering and null behavior, pick\ncollectors by map semantics, use primitive streams for primitive totals, and treat parallel streams\nas a design choice rather than a default optimization.\n\nIt also tells the agent to check the project Java version first. The right stream code for Java 8\nmay be different from the right code for Java 17, Java 21, or Java 24.\n\n## Contents\n\n- [Getting Started](#getting-started)\n- [Why This Exists](#why-this-exists)\n- [Common Stream Mistakes](#common-stream-mistakes)\n- [What It Helps With](#what-it-helps-with)\n- [How It's Evaluated](#how-its-evaluated)\n- [Origin](#origin)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Getting Started\n\n### 1. Install\n\nInstall the published Tessl plugin using the option that fits your setup:\n\n| Tool | Command |\n| --- | --- |\n| npm | `npx tessl i martinfrancois/java-streams` |\n| yarn | `yarn dlx tessl i martinfrancois/java-streams` |\n| pnpm | `pnpx tessl i martinfrancois/java-streams` |\n| bun | `bunx tessl i martinfrancois/java-streams` |\n| Tessl CLI | `tessl i martinfrancois/java-streams` |\n\n### 2. Use It\n\nAgents that support skill auto-selection, such as\n[Codex](https://developers.openai.com/codex/skills) and\n[Claude Code](https://code.claude.com/docs/en/skills), can choose this skill automatically from the\ntask or code context. The task does not need to say `stream` by name.\n\nIt can trigger when Java code uses streams, collectors, primitive streams, `findFirst()` /\n`findAny()`, match terminal operations, `flatMap`, `mapMulti`, `joining`, `min` / `max`, `sum`,\n`groupingBy`, `toMap`, `partitioningBy`, `teeing`, `takeWhile` / `dropWhile`, or parallel stream\nbehavior.\n\nFor important stream-heavy work, you can still name the skill explicitly:\n\n```text\nUse $java-streams to implement this Java feature with stream and collector best practices.\n```\n\nFor cleanup work:\n\n```text\nUse $java-streams to clean up this Java stream chain without changing behavior.\n```\n\nFor reviews:\n\n```text\nUse $java-streams to review this Java stream code and suggest any fixes.\n```\n\n## Why This Exists\n\nAI can write Java code that looks modern because it uses streams. If you do not know streams very\nwell, that code can look plausible in review. But looking plausible is not enough: the code can\nstill use the wrong kind of concurrency, build lists it does not need, use `null` as a hidden signal,\nfail when keys are duplicated, or choose a terminal operation that does not match the real intent.\n\nThis skill helps the agent write stream code that is easier to read, safer to change, and often more\nefficient. It pushes the agent toward the stream operation that matches the job, such as `anyMatch`\ninstead of collecting a list just to check if a match exists. It also helps the agent review existing\nstream code and explain what should be fixed.\n\nFor example, imagine code that checks a user's favorite products against a remote inventory API.\nThat API call blocks while it waits for the remote service. The code should:\n\n- check products in the user's favorite order;\n- run at most 8 stock checks at the same time;\n- keep only products that are in stock;\n- sort the final result by product name.\n\nWithout the skill, the generated code used two different approaches. Both looked reasonable at\nfirst, but both had important problems.\n\n### Version 1: `parallelStream()`\n\n```java\nprivate static final Semaphore STOCK_CHECKS = new Semaphore(8);\n\nList\u003cProduct\u003e favoriteProducts(User user) {\n    return user.favoriteProducts().parallelStream()\n            .filter(product -\u003e {\n                try {\n                    STOCK_CHECKS.acquire();\n                    return InventoryApi.check(product.sku());\n                } catch (InterruptedException e) {\n                    Thread.currentThread().interrupt();\n                    throw new RuntimeException(e);\n                } finally {\n                    STOCK_CHECKS.release();\n                }\n            })\n            .sorted(Comparator.comparing(Product::name))\n            .toList();\n}\n```\n\nThis keeps the basic filter and sort behavior, but it is still not a good solution:\n\n- `parallelStream()` uses Java's common fork-join pool. The\n  [`ForkJoinPool` Javadoc](https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/util/concurrent/ForkJoinPool.html)\n  says the pool can adjust its worker threads in some cases, but those adjustments are not\n  guaranteed for blocked I/O. That makes it a poor default for blocking remote API calls.\n- In this example, the requested limit is per call to `favoriteProducts(user)`, but the static\n  semaphore is shared by every call to the method. Two users calling it at the same time can block\n  each other instead of each call getting its own limit of up to 8 stock checks.\n- The concurrency limit is separate from the stream chain, which makes the code harder to reason\n  about.\n\n### Version 2: Virtual Threads And A Semaphore\n\n```java\nList\u003cProduct\u003e favoriteProducts(User user) {\n    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {\n        var permits = new Semaphore(8);\n\n        return user.favoriteProducts().stream()\n                .map(product -\u003e executor.submit(() -\u003e {\n                    permits.acquire();\n                    try {\n                        return InventoryApi.check(product.sku()) ? product : null;\n                    } finally {\n                        permits.release();\n                    }\n                }))\n                .map(FavoriteProducts::getUnchecked)\n                .filter(Objects::nonNull)\n                .sorted(Comparator.comparing(Product::name))\n                .toList();\n    }\n}\n\nprivate static Product getUnchecked(Future\u003cProduct\u003e future) {\n    try {\n        return future.get();\n    } catch (InterruptedException e) {\n        Thread.currentThread().interrupt();\n        throw new RuntimeException(e);\n    } catch (ExecutionException e) {\n        throw new RuntimeException(e.getCause());\n    }\n}\n```\n\nThis avoids `parallelStream()`, and it limits how many checks are active at once. But it still has\nproblems:\n\n- It submits one task for every product before it starts collecting results. With a large list, that\n  can create a large backlog of queued tasks even though only 8 checks run at once.\n- It uses `null` as a hidden signal for \"not in stock.\" A reader has to remember that special\n  meaning until the later `filter(Objects::nonNull)`. If someone changes the stream chain before\n  that filter, the code can easily break.\n\n### With The Skill: `Gatherers.mapConcurrent`\n\nWhen the project uses Java 24 or newer, `Gatherers.mapConcurrent` gives a bounded concurrent stream\noperation for this kind of blocking per-element work:\n\n```java\nList\u003cProduct\u003e favoriteProducts(User user) {\n    return user.favoriteProducts().stream()\n            .gather(Gatherers.mapConcurrent(8,\n                    product -\u003e Map.entry(product, InventoryApi.check(product.sku()))))\n            .filter(Map.Entry::getValue)\n            .map(Map.Entry::getKey)\n            .sorted(Comparator.comparing(Product::name))\n            .toList();\n}\n```\n\nThis version is clearer:\n\n- It keeps the limit of 8 checks inside the stream chain.\n- It uses bounded concurrency with backpressure: keep only a limited amount of work in flight, then\n  start more work as earlier checks finish.\n- It keeps each product together with its stock-check result.\n- It filters and sorts in a clear order.\n\n## Common Stream Mistakes\n\nThe skill also helps with mistakes such as:\n\n- collecting filtered elements into a list, then checking `isEmpty()` or reading the first item;\n- using `count() \u003e 0` instead of `anyMatch(...)`;\n- using `sorted(...).findFirst()` instead of `min(...)` or `max(...)`;\n- mapping to a list and then calling `String.join(...)` instead of using `Collectors.joining(...)`;\n- using boxed `reduce(...)` where a primitive stream terminal operation is clearer;\n- building nested sets or lists inside a `map(...)`, then flattening afterward;\n- using `toMap(...)` without a merge function when duplicate keys are possible;\n- forgetting that natural sorting throws when `null` reaches the comparator;\n- making casual `parallelStream()` changes without checking data size, CPU cost, shared state,\n  ordering, or blocking IO.\n\nThe goal is not to turn every loop into a stream. The goal is to use streams and collectors when\nthey make the code clearer, safer, or easier to check.\n\n## What It Helps With\n\nGood fit:\n\n- replacing collect-then-inspect, count-for-existence, or sort-then-first patterns;\n- choosing `findFirst()` or `findAny()` without changing ordering semantics;\n- using `flatMap` for nested collections and `Optional::stream` for `Stream\u003cOptional\u003cT\u003e\u003e`;\n- using `Collectors.joining`, `groupingBy`, `mapping`, `counting`, `summingInt` /\n  `summingLong` / `summingDouble`, `summarizingInt` / `summarizingLong` /\n  `summarizingDouble`, `partitioningBy`, `toMap`, and `teeing` correctly;\n- selecting primitive streams and primitive stream terminal operations for primitive aggregation;\n- avoiding null-sensitive sorting and duplicate-key `toMap` failures;\n- deciding whether `parallelStream()` is actually appropriate;\n- choosing Java-version-compatible APIs such as `takeWhile`, `mapMulti`, `Stream.toList()`, and\n  gatherers.\n\nPoor fit:\n\n- broad Java style enforcement unrelated to streams or collectors;\n- replacing straightforward stateful loops with hard-to-read stream tricks;\n- large API redesigns or new dependencies without maintainer agreement;\n- changing business behavior just to make code look more functional.\n\n## How It's Evaluated\n\nThe skill is tested on Java stream implementation, review, and cleanup tasks. Each task is run\nwithout the skill and with the skill, then scored on whether the agent keeps the requested behavior\nwhile choosing better stream and collector code.\n\nThe evals cover common places where agents write plausible-looking but weak Java: collecting before\nchecking existence, using the wrong terminal operation, losing encounter order, mishandling\nduplicate keys or nulls, overusing `parallelStream()`, and missing Java-version-specific APIs such\nas `takeWhile`, `teeing`, `mapMulti`, `Stream.toList()`, and gatherers.\n\nCurrent published scores are shown on the\n[Tessl plugin](https://tessl.io/registry/martinfrancois/java-streams).\n\n## Origin\n\nThe stream examples and pattern catalog are based on the code examples from François Martin's conference\ntalk [\"I didn't know you could do that with Java Streams\"](https://fmartin.ch/session/i-didnt-know-you-could-do-that-with-java-streams),\nwith the public example source here:\n\u003chttps://github.com/martinfrancois/jfokus-2026/blob/main/code.md\u003e.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for local validation, eval design rules, commit-message\nformat, and release workflow details.\n\nAI-assisted contributions are welcome when they are transparent, reviewed, and owned by a human. See\n[AI_CONTRIBUTION_POLICY.md](AI_CONTRIBUTION_POLICY.md).\n\nFor suspected vulnerabilities, use the private reporting path in [SECURITY.md](SECURITY.md).\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmartinfrancois%2Fjava-streams-skill","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmartinfrancois%2Fjava-streams-skill","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmartinfrancois%2Fjava-streams-skill/lists"}