cat posts/2024-07-18-delaying-exceptions-while-looping.md
Delaying exceptions while looping in Java
##Introduction
By default, Java will break a loop when an exception is thrown while iterating:
public class BreakAndThrow {
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 3 == 2) {
throw new RuntimeException("Something went wrong");
}
}
}
}- This loop will be broken on iteration #3 because a
RuntimeExceptionis thrown.
It often makes sense to handle exceptions inside the loop and keep looping after an exception is thrown:
public class HandleAndContinue {
public void run() {
for (int i = 0; i < 10; i++) {
try {
if (i % 3 == 2) {
throw new RuntimeException("Something went wrong");
}
} catch (Exception e) {
// TODO Handle the exception.
}
}
}
}- The
forloop is no longer broken thanks to thiscatchblock. - Don’t swallow the exception! If you’re not sure yet how to deal with it, log it.
##Delaying exceptions
What if you need to propagate or "bubble up" exceptions that are thrown inside a loop without breaking that loop? Let’s see how exceptions thrown from a loop can be delayed and eventually thrown after the loop successfully completes.
First, we’ll need a custom exception class:
public class DelayedException extends RuntimeException {
public DelayedException(String message) {
super(message);
}
}Then, we’ll use that custom exception from a utility class:
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
public class DelayedThrower {
public static void throwEventually(String exceptionMsg, Consumer<List<Exception>> exceptionsConsumer) {
Objects.requireNonNull(exceptionsConsumer, "The exceptions consumer must be not null");
List<Exception> exceptions = new ArrayList<>();
exceptionsConsumer.accept(exceptions);
if (exceptions.isEmpty()) {
return;
}
DelayedException delayedException = new DelayedException(exceptionMsg);
for (Exception e : exceptions) {
if (e instanceof DelayedException && e.getSuppressed().length > 0) {
for (Throwable t : e.getSuppressed()) {
delayedException.addSuppressed(t);
}
} else {
delayedException.addSuppressed(e);
}
}
throw delayedException;
}
}- Exceptions thrown from the loop will be stored into this collection until the loop completes.
- If no exceptions were thrown from the loop, the method can exit immediately.
- This is the exception that will eventually be thrown.
- This makes it possible to use nested
DelayedThrower#throwEventuallycalls. - Exceptions thrown from the loop are added as suppressed exceptions to the
DelayedException.
Now, it’s time to delay exceptions!
public class ThrowEventually {
public void run() {
DelayedThrower.throwEventually("Exceptions were thrown while looping", exceptions -> {
for (int i = 0; i < 10; i++) {
try {
if (i % 3 == 2) {
throw new RuntimeException(String.format("Something went wrong [i=%d]", i));
}
} catch (Exception e) {
exceptions.add(e);
}
}
});
}
}- When this method completes, a
DelayedExceptionwith 3 suppressed exceptions will be thrown.
If the DelayedException is logged, it should look like this:
com.gwenneg.blog.delayed.DelayedException: Exceptions were thrown while looping
at com.gwenneg.blog.delayed.DelayedThrower.throwEventually(DelayedThrower.java:21)
at com.gwenneg.blog.delayed.ThrowEventually.run(ThrowEventually.java:6)
[...]
Suppressed: java.lang.RuntimeException: Something went wrong [i=2]
at com.gwenneg.blog.delayed.ThrowEventually.lambda$run$0(ThrowEventually.java:10)
at com.gwenneg.blog.delayed.DelayedThrower.throwEventually(DelayedThrower.java:15)
... 5 more
Suppressed: java.lang.RuntimeException: Something went wrong [i=5]
at com.gwenneg.blog.delayed.ThrowEventually.lambda$run$0(ThrowEventually.java:10)
at com.gwenneg.blog.delayed.DelayedThrower.throwEventually(DelayedThrower.java:15)
... 5 more
Suppressed: java.lang.RuntimeException: Something went wrong [i=8]
at com.gwenneg.blog.delayed.ThrowEventually.lambda$run$0(ThrowEventually.java:10)
at com.gwenneg.blog.delayed.DelayedThrower.throwEventually(DelayedThrower.java:15)
... 5 moreThat’s all I have for today. Happy looping!