PUBLIC OBJECT

Fancy Flow Control: Did it throw?

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.