1

I have a question about Java method introspection, specifically concerning exceptions. Say I have the following code:

private String getCustomReportResponse(HttpsURLConnection customReportConnection) {
    int responseCode = 0;

    try {
        responseCode = customReportConnection.getResponseCode();
        return httpResponseBodyExtractor.extractResponseBodyFrom(customReportConnection);
    } catch (IOException e) {
        translateIntoRelevantException(responseCode, e);
    }
}

Let's say both statements in the try block are capable of throwing an IOException - in which case, the translateIntoRelevantException method is invoked, which looks like this:

private void translateIntoRelevantException(int responseCode, IOException e) {
    if(is5xxResponseCode(responseCode)) {
        throw new ServerResponseException("Could not generate report - returned response code " + responseCode, e);
    }

    throw new ReportException("GeminiReportException: Unable to parse response from HTTP body when requesting custom Gemini report.", e);
}

So, whatever happens, either a String is returned, or an exception is thrown. Except the getCustomReportResponse method does not compile without adding a return statement after the catch block, which is absolutely unreachable. In fact, if I put the contents of translateIntoRelevantException inside the catch block, it compiles, which seems daft to me.

I should add, the exceptions being thrown are runtime exceptions, but I've also tried making them checked exceptions, but the problem persisted.

Could someone please explain why?

default locale
  • 13,035
  • 13
  • 56
  • 62

2 Answers2

1

This is a common problem that "rethrow" helper methods face.

The compiler does not know (and there is no way to indicate) that the method translateIntoRelevantException will never return.

As such, it thinks that there is a code-path that continues after try block. So you have to put in a "dead-code" return null (or throw new RuntimeException("should never come here").

You don't have to put it after the try block, you can put it inside the catch.

try {
    responseCode = customReportConnection.getResponseCode();
    return httpResponseBodyExtractor.extractResponseBodyFrom(customReportConnection);
} catch (IOException e) {
    translateIntoRelevantException(responseCode, e);
    throw new RuntimeException("should never come here");
}

It's probably prettier to have the helper just return the exception instead of throwing it. Then you can do

 throw translateIntoRelevantException(responseCode, e);
Community
  • 1
  • 1
Thilo
  • 257,207
  • 101
  • 511
  • 656
  • Thanks for answering. So if I move the contents of the method (translateIntoRelevantException) into the catch block (the contents are implicitly void, as they throw an exception in 100% of cases), it works. I thought the compiler would be capable of looking inside the method, and seeing that, in all cases, an exception is thrown. It just seems like the compiler is penalising modularisation, which is strange. – Danny Noam 父 Feb 02 '17 at 12:20
  • 1
    No, the compiler does not look *into* methods, just at the declared signature. And there is no way to declare that a method never returns (in Scala, for example, you can say the return type is `Nothing` to do just that). Looking at method implementations may work for private methods in the same class, but as soon as dynamic method dispatch (sub-classing) is involved, that won't work anymore. Having to distinguish between the two seems not worth the trouble. – Thilo Feb 02 '17 at 12:23
1

Compilation of getCustomReportResponse should not rely on the implementation of translateIntoRelevantException for multiple reasons:

  • implementation of translateIntoRelevantException might not be available (it could be in a separate class, in a separate library);
  • otherwise any change in translateIntoRelevantException could break all of the calling methods.

As an alternative you can return an exception and then throw it in a client code:

private IOException translateIntoRelevantException(int responseCode, IOException e) {
    if(is5xxResponseCode(responseCode)) {
        return new ServerResponseException("Could not generate report - returned response code " + responseCode, e);
    }

    return new ReportException("GeminiReportException: Unable to parse response from HTTP body when requesting custom Gemini report.", e);
}

then call it like this:

throw translateIntoRelevantException(responseCode, e);
default locale
  • 13,035
  • 13
  • 56
  • 62