diff --git a/core-java-modules/core-java-streams-5/pom.xml b/core-java-modules/core-java-streams-5/pom.xml
index fde48978c9..dc97d81b3d 100644
--- a/core-java-modules/core-java-streams-5/pom.xml
+++ b/core-java-modules/core-java-streams-5/pom.xml
@@ -38,6 +38,11 @@
3.12.0
test
+
+ io.vavr
+ vavr
+ ${vavr.version}
+
@@ -66,6 +71,7 @@
12
12
+ 0.10.2
\ No newline at end of file
diff --git a/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/CustomCollector.java b/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/CustomCollector.java
new file mode 100644
index 0000000000..dea3d3f44c
--- /dev/null
+++ b/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/CustomCollector.java
@@ -0,0 +1,38 @@
+package com.baeldung.aggregateexception;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collector;
+
+public class CustomCollector {
+ private final List results = new ArrayList<>();
+ private final List exceptions = new ArrayList<>();
+
+ public static Collector> of(Function mapper) {
+ return Collector.of(
+ CustomCollector::new,
+ (collector, item) -> {
+ try {
+ R result = mapper.apply(item);
+ collector.results.add(result);
+ } catch (Exception e) {
+ collector.exceptions.add(e);
+ }
+ },
+ (left, right) -> {
+ left.results.addAll(right.results);
+ left.exceptions.addAll(right.exceptions);
+ return left;
+ }
+ );
+ }
+
+ public List getResults() {
+ return results;
+ }
+
+ public List getExceptions() {
+ return exceptions;
+ }
+}
diff --git a/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/CustomMapper.java b/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/CustomMapper.java
new file mode 100644
index 0000000000..920c963337
--- /dev/null
+++ b/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/CustomMapper.java
@@ -0,0 +1,17 @@
+package com.baeldung.aggregateexception;
+
+import com.baeldung.aggregateexception.entity.Result;
+
+import java.util.function.Function;
+
+public class CustomMapper {
+ public static Function> mapper(Function func) {
+ return arg -> {
+ try {
+ return new Result(func.apply(arg));
+ } catch (Exception e) {
+ return new Result(e);
+ }
+ };
+ }
+}
diff --git a/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/entity/Result.java b/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/entity/Result.java
new file mode 100644
index 0000000000..b723c3e510
--- /dev/null
+++ b/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/entity/Result.java
@@ -0,0 +1,26 @@
+package com.baeldung.aggregateexception.entity;
+
+import java.util.Optional;
+
+public class Result {
+ private Optional result;
+ private Optional exception;
+
+ public Result(R result) {
+ this.result = Optional.of(result);
+ this.exception = Optional.empty();
+ }
+
+ public Result(E exception) {
+ this.exception = Optional.of(exception);
+ this.result = Optional.empty();
+ }
+
+ public Optional getResult() {
+ return result;
+ }
+
+ public Optional getException() {
+ return exception;
+ }
+}
diff --git a/core-java-modules/core-java-streams-5/src/test/java/com/baeldung/aggregateexception/AggregateExceptionHandlerUnitTest.java b/core-java-modules/core-java-streams-5/src/test/java/com/baeldung/aggregateexception/AggregateExceptionHandlerUnitTest.java
new file mode 100644
index 0000000000..6410645d2a
--- /dev/null
+++ b/core-java-modules/core-java-streams-5/src/test/java/com/baeldung/aggregateexception/AggregateExceptionHandlerUnitTest.java
@@ -0,0 +1,150 @@
+package com.baeldung.aggregateexception;
+
+import com.baeldung.aggregateexception.entity.Result;
+import io.vavr.control.Either;
+import io.vavr.control.Try;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+
+
+public class AggregateExceptionHandlerUnitTest {
+ private static final Logger logger = LoggerFactory.getLogger(AggregateExceptionHandlerUnitTest.class);
+
+ @Test
+ public void givenExtractedMethod_whenFoundEx_thenSuppressExIntoRuntimeEx() {
+ String[] strings = {"1", "2", "3", "a", "b", "c"};
+ RuntimeException runEx = Arrays.stream(strings)
+ .map(str -> callProcessThrowsExAndNoOutput(str))
+ .filter(Objects::nonNull)
+ .reduce(new RuntimeException("Errors Occurred"), (o1, o2) -> {
+ o1.addSuppressed(o2);
+ return o1;
+ });
+ processExceptions(runEx);
+ assertEquals("Errors Occurred", runEx.getMessage());
+ assertEquals(3, runEx.getSuppressed().length);
+ }
+ @Test
+ public void givenTryCatchInPipeline_whenFoundEx_thenSuppressExIntoRuntimeEx() {
+ String[] strings = {"1", "2", "3", "a", "b", "c"};
+ RuntimeException runEx = Arrays.stream(strings).map(str -> {
+ try {
+ processThrowsExAndNoOutput(str);
+ return null;
+ } catch (RuntimeException e) {
+ return e;
+ }
+ }).filter(Objects::nonNull)
+ .collect(Collectors.collectingAndThen(Collectors.toList(), list -> {
+ RuntimeException runtimeException = new RuntimeException("Errors Occurred");
+ list.forEach(runtimeException::addSuppressed);
+ return runtimeException;
+ }));
+ processExceptions(runEx);
+ assertEquals("Errors Occurred", runEx.getMessage());
+ assertEquals(3, runEx.getSuppressed().length);
+ }
+
+ @Test
+ public void givenProcessMethod_whenStreamResultHasExAndOutput_thenHandleExceptionListAndOutputList() {
+ List strings = List.of("1", "2", "3", "a", "b", "c");
+ Map map = strings.stream()
+ .map(s -> processReturnsExAndOutput(s))
+ .collect(Collectors.partitioningBy(o -> o instanceof RuntimeException, Collectors.toList()));
+
+ List exceptions = (List)map.getOrDefault(Boolean.TRUE, List.of());
+ List results = (List)map.getOrDefault(Boolean.FALSE, List.of());
+ handleExceptionsAndOutputs(exceptions, results);
+ }
+
+ @Test
+ public void givenCustomMapper_whenStreamResultHasExAndSuccess_thenHandleExceptionListAndOutputList() {
+ List strings = List.of("1", "2", "3", "a", "b", "c");
+ strings.stream()
+ .map(CustomMapper.mapper(Integer::parseInt))
+ .collect(Collectors.collectingAndThen(Collectors.toList(), list -> handleErrorsAndOutputForResult(list)));
+ }
+
+ @Test
+ public void givenCustomCollector_whenStreamResultHasExAndSuccess_thenHandleAggrExceptionAndResults() {
+ String[] strings = {"1", "2", "3", "a", "b", "c"};
+ Arrays.stream(strings)
+ .collect(Collectors.collectingAndThen(CustomCollector.of(Integer::parseInt),
+ col -> handleExAndResults(col.getExceptions(), col.getResults())));
+ }
+
+ @Test
+ public void givenVavrEitherAndTry_whenStreamResultHasExAndSuccess_thenHandleExceptionListAndOutputList() {
+ List strings = List.of("1", "2", "3", "a", "b", "c");
+ strings.stream()
+ .map(str -> Try.of(() -> Integer.parseInt(str)).toEither())
+ .collect(Collectors.collectingAndThen(Collectors.partitioningBy(Either::isLeft, Collectors.toList()),
+ map -> handleErrorsAndOutputForEither(map)));
+ }
+
+ private static void processThrowsExAndNoOutput(String input) {
+ //throw exception when input is "a", "b", "c"
+ if (input.matches("[a-c]")) {
+ throw new RuntimeException("Downstream method throws exception for " + input);
+ }
+ }
+ private static RuntimeException callProcessThrowsExAndNoOutput(String input) {
+ try {
+ processThrowsExAndNoOutput(input);
+ return null;
+ } catch (RuntimeException e) {
+ return e;
+ }
+ }
+
+ private static Object processReturnsExAndOutput(String input) {
+ logger.info("call a downstream method that returns an Integer");
+ try {
+ return Integer.parseInt(input);
+ } catch (Exception e) {
+ return new RuntimeException("Exception in processWithReturnOutput for " + input, e);
+ }
+ }
+
+ private static void processExceptions(Throwable throwable) {
+ logger.error("Process Exception" + throwable.getMessage());
+ }
+
+ private static void handleExceptionsAndOutputs(List exs, List output) {
+ logger.info("number of exceptions " + exs.size() + " number of outputs " + output.size());
+ }
+
+ private static String handleExAndResults(List ex, List results ) {
+ logger.info("handle aggregated exceptions and results" + ex.size() + " " + results.size());
+ return "Exceptions and Results Handled";
+ }
+
+ private static String handleErrorsAndOutputForEither(Map>> map) {
+ logger.info("handle errors and output");
+ map.getOrDefault(Boolean.TRUE, List.of()).forEach(either -> logger.error("Process Exception " + either.getLeft()));
+
+ map.getOrDefault(Boolean.FALSE, List.of()).forEach(either -> logger.info("Process Result " + either.get()));
+ return "Errors and Output Handled";
+ }
+
+ private static String handleErrorsAndOutputForResult(List> successAndErrors) {
+ logger.info("handle errors and output");
+ successAndErrors.forEach(result -> {
+ if (result.getException().isPresent()) {
+ logger.error("Process Exception " + result.getException().get());
+ } else {
+ logger.info("Process Result" + result.getResult().get());
+ }
+ });
+ return "Errors and Output Handled";
+ }
+}
\ No newline at end of file