{"id":16774164,"url":"https://github.com/hgwood/java8-streams-and-exceptions","last_synced_at":"2025-10-30T15:12:27.855Z","repository":{"id":14222522,"uuid":"16929446","full_name":"hgwood/java8-streams-and-exceptions","owner":"hgwood","description":"Experiments with dealing with exceptions while using the Stream API","archived":false,"fork":false,"pushed_at":"2016-08-30T14:11:59.000Z","size":28,"stargazers_count":34,"open_issues_count":0,"forks_count":7,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-04T16:53:36.009Z","etag":null,"topics":["exceptions","java","streams"],"latest_commit_sha":null,"homepage":null,"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/hgwood.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-02-17T22:55:07.000Z","updated_at":"2024-10-25T15:33:59.000Z","dependencies_parsed_at":"2022-08-19T20:10:12.983Z","dependency_job_id":null,"html_url":"https://github.com/hgwood/java8-streams-and-exceptions","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hgwood/java8-streams-and-exceptions","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hgwood%2Fjava8-streams-and-exceptions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hgwood%2Fjava8-streams-and-exceptions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hgwood%2Fjava8-streams-and-exceptions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hgwood%2Fjava8-streams-and-exceptions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hgwood","download_url":"https://codeload.github.com/hgwood/java8-streams-and-exceptions/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hgwood%2Fjava8-streams-and-exceptions/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270831965,"owners_count":24653445,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-17T02:00:09.016Z","response_time":129,"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":["exceptions","java","streams"],"created_at":"2024-10-13T06:48:16.428Z","updated_at":"2025-10-30T15:12:22.803Z","avatar_url":"https://github.com/hgwood.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"Java 8's new Stream API, being FP-inspired, doesn't play with exceptions very well.\n\nFor example, let's say we want to map a set of URI strings to URI objects:\n\n```java\nuriStrings.stream().map(URI::create).collect(toList());\n```\n\nIf one of the strings is not a valid URL, `map()` still runs OK because it does nothing but register the map operation,\nhowever, `collect()` does call `URI::create`, which throws an `IllegalArgumentException` and the overall operation\nfails without hope of retrieving the URIs that were valid. A custom collector cannot solve this issue because\n`collect()` computes the value *before* it hands it to the collector.\n\nThis is my attempt to solve this problem. The idea is that the collector has to be able to\ncontrol when the exception-throwing function is called in order to catch exceptions and process them, so the call has\nto be delayed. To achieve this, values are mapped to supplier of values using a method I called `lazy`:\n\n```java\npublic static \u003cT, R\u003e Function\u003cT, Supplier\u003cR\u003e\u003e lazy(Function\u003cT, R\u003e f) {\n    return input -\u003e () -\u003e f.apply(input);\n}\n```\n\nNotice that `f.apply()` is never called inside lazy, nor is it called when the returned function is called. Mapping a\n`lazy()`-wrapped `A` to `B` function onto a stream of `A`s results in a stream of suppliers of `B`s. A downstream\ncollector therefore gets to work with suppliers instead of values (URIs in the example). The collector can then choose\nto call (or not call) `Supplier::get` and properly catch exceptions thrown by it.\n\n# Examples\n\nAll the following examples take this data as input. The first and third strings are valid URIs: `URI::create` parses them OK. The second one make the same method choke and throw an `IllegalArgumentException`.\n\n```java\nCollection\u003cString\u003e data = asList(\"http://elevated\", \"invalid\\nurl\", \"http://abstractions\");\n```\n\nLet's review different ways to manage a map operation of `URI::create` on this data.\n\n## Discarding failures\n\nThe `discarding` collector swallows exceptions of specified types. Exception types have to be explicitly passed in order to propagate other exceptions. The following code results in a stream containing `\"http://elevated\"` and `\"http://abstractions\"`.\n\n```java\ndata.stream()\n    .map(lazy(URI::create))\n    .collect(discarding(IllegalArgumentException.class));\n```\n\n## Silently stopping the computation on failure, returning partial results\n\nThe `upTo` collector also swallows exception, but it does not continue to read the input once the first exception is thrown. The following code results in a stream containing `\"http://elevated\"`.\n\n```java\ndata.stream()\n    .map(lazy(URI::create))\n    .collect(upTo(IllegalArgumentException.class));\n```\n\n## Throwing on failure, keeping partial results\n\nThe `upToAndThrow` collector wraps the first exception (of specified types) it encounters in a `FailFastCollectException`. The interesting bit is that the `FailFastCollectException` gives you access to the results that were successfully computed prior to the failure. In the following code, `e.getResults()` returns a stream containing `\"http://elevated\"`.\n\n```java\ntry {\n    data.stream()\n        .map(lazy(URI::create))\n        .collect(upToAndThrow(IllegalArgumentException.class));\n} catch (FailFastCollectException e) {\n    e.getCause();\n    e.getResults();\n}\n```\n\n## Throwing at the end, keeping partial results\n\nThe `throwingAtEnd` collector reads the whole input, collecting both results and exceptions as it goes. Once the stream is fully read, it throws a `CollectException` that gives access to what it's been collecting. In the following code, `e.getResults()` returns a stream containing `\"http://elevated\"` and `\"http://abstractions\"` while `e.getCauses()` return a collection containing an `IllegalArgumentException` instance.\n\n```java\ntry {\n    data.stream()\n        .map(lazy(URI::create))\n        .collect(throwingAtEnd(IllegalArgumentException.class));\n} catch (CollectException e) {\n    e.getCauses();\n    e.getResults();\n}\n```\n\n# Dealing With Checked Exceptions\n\nThis method can also be used with checked exceptions, if they are first wrapped into unchecked ones.\n\n```java\ndata.stream()\n    .map(lazy(sneaky(URI::new, e -\u003e new CustomRuntimeException(e))))\n    .collect(discarding(CustomRuntimeException.class));\n```\n\nThe above code is simplified if the library provides a default wrapping.\n\n```java\ndata.stream()\n    .map(lazy(sneaky(URI::new)))\n    .collect(discardingFailures());\n```\n\n# Acknowledgement\n\nThe motivation for this experiment was triggered by the work of Yohan Legat, a co-worker at \n[Zenika](http://zenika.com). His work is also on \n[GitHub](https://github.com/Zenika/Blogs/tree/master/20140214-Try) \nand he wrote a article (in French) on \n[Zenika's technical blog](http://blog.zenika.com/index.php?post/2014/02/19/Repenser-la-propagation-des-exceptions-avec-Java-8).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhgwood%2Fjava8-streams-and-exceptions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhgwood%2Fjava8-streams-and-exceptions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhgwood%2Fjava8-streams-and-exceptions/lists"}