Exceptions are powerful control flow: mainline code handles the mainline cases, and exception cases can be handled elsewhere. But sometimes you want to observe whether an exception occurred, without necessarily handling that exception.
For example, suppose we're writing a service that sends webhooks to a far-away webserver. Webhooks are naturally flaky: there are usually many target servers, each managed a different team with a different expectation of availability and correctness.
Our method wants to run webhooks and keep statistics on their results:
public int sendWebhook(String url, String request)
throws IOException {
try {
int statusCode = httpPost(url, request);
if (statusCode >= 400) {
failureCount++;
}
return statusCode;
} catch (IOException e) {
errorCount++;
throw e;
}
}
What happens if httpPost()
throws an unchecked exception, like WebServiceException
? Well, we can handle that too. And also Error
so that assertion failures aren't dropped:
public int sendWebhook(String url, String request)
throws IOException {
try {
int statusCode = httpPost(url, request);
if (statusCode >= 400) {
failureCount++;
}
return statusCode;
} catch (IOException | RuntimeException | Error e) {
errorCount++;
throw e;
}
}
This works. But it's unfortunate to list the three different Throwable
types that can pass-through this method, especially if those types could change. Should we add another checked exception like URISyntaxException
, it's easy to lose those failures:
public int sendWebhook(String url, String request)
throws IOException, URISyntaxException {
try {
int statusCode = httpPost(new URI(url), request);
if (statusCode >= 400) {
failureCount++;
}
return statusCode;
} catch (IOException | RuntimeException | Error e) {
errorCount++; // Forgot URISyntaxException !
throw e;
}
}
We can use a try/finally
instead:
public int sendWebhook(String url, String request)
throws IOException, URISyntaxException {
boolean error = true;
try {
int statusCode = httpPost(new URI(url), request);
if (statusCode >= 400) {
failureCount++;
}
error = false;
return statusCode;
} finally {
if (error) {
errorCount++;
}
}
}
If anything in the try
block throws, we won't get to set error
to false
and so errorCount
will be incremented in the finally
block.
Finally, a use for try/finally
that isn't calling close()
!
Update: there's a better way
Wouter Coekaerts points out that since Java 7, you can just throw Throwable
and javac does the right thing.
public int sendWebhook(String url, String request)
throws IOException, URISyntaxException {
try {
int statusCode = httpPost(new URI(url), request);
if (statusCode >= 400) {
failureCount++;
}
return statusCode;
} catch (Throwable t) {
errorCount++;
throw t;
}
}
So we can dismiss this trick as neat, but obsolete.