Exception handling in switch (Preview)

Dan Heidinga dan.heidinga at oracle.com
Mon Apr 22 14:05:08 UTC 2024


My reading of the JEP is that this is that it’s about treating all the possible results of evaluating the selector uniformly – both the normal results and the exceptional ones.

Being able to treat the results uniformly makes it easier to be precise about what exceptions should be handled in each way.  With today’s try/catch, there is no way to distinguish handling of an exception thrown by the selector from one due to the case handling code.  This JEP allows those two cases to be distinguished.

As to refactoring, my impression is that the new case throws is a win as it allows the handling code to be scoped closer to the potential source of the exception.

More responses in line.

From: amber-spec-experts <amber-spec-experts-retn at openjdk.org> on behalf of Tagir Valeev <amaembo at gmail.com>
Date: Saturday, April 20, 2024 at 4:01 AM
To: Angelos Bimpoudis <angelos.bimpoudis at oracle.com>
Cc: amber-spec-experts <amber-spec-experts at openjdk.java.net>
Subject: Re: Exception handling in switch (Preview)
Dear experts,

looking into this proposal, I'm really not convinced that Java needs it. We already have try-catch statements, and it sounds strange to provide another way to express the same semantics. I don't see what the new construct adds, aside from a bit of syntactic sugar. On the other hand, it creates a new source of subtle bugs, especially when exceptions are unchecked. E.g., consider:

switch(a.b().c().d()) {
case ...
case throws RuntimeException ex -> handle(ex);
}

Now, one may want to refactor the code, extracting a.b(), a.b().c(), or the whole a.b().c().d() to a separate variable for clarity, or to avoid a long line.
This action is usually safe, and it was totally safe in switches so far (even with patterns and case null). Now, it's not safe, as exceptions thrown from the extracted part are not handled by the 'case throws' branch.
I don't see a good way to perform this refactoring in a semantically equivalent way. The only possibility I see is to duplicate the exception handler in the external catch:

try {
  var ab = a.b();
  switch(ab.c().d()) {
  case ...
  case throws RuntimeException ex -> handle(ex);
  }
}
catch(RuntimeException ex) {
  handle(ex); // duplicated code
}

This doesn’t mean quite the same thing as the new try/catch block will catch RuntimeExceptions thrown by the “case …” in addition to those thrown by the selector (“ab.c().d()”).  I think we can use more switches to make the refactoring:

var ab = switch(a.b()) {
  case Object o -> o; // need more precise type than Object
  case throws RuntimeException ex -> handle(ex);
}
switch(ab.c().d()) {
  case ...
  case throws RuntimeException ex -> handle(ex);
 }


As switch selector does not allow using several expressions or to declare new variables, extract/inline refactorings can easily become very painful, or cause subtle bugs if not performed correctly.
Note that it's not a problem inside usual try-catch statement (*), as you can easily add or remove more statements inside the try-body.

(*) Except resource declaration, but it's rarely a problem, and in some cases it's still possible to extract parts as separate resources, because you can declare several of them

I think, instead of repurposing switch to be another form of try-catch we could add more love to try-catch allowing it to be an expression with yields in branches. The proposed JEP allows something like this:

Integer toIntOrNull(String s) {
  return switch(Integer.parseInt(s)) {
    case int i -> i;
    case throws NumberFormatException _ -> null;
  }
}

But we are still limited by a single expression in the selector. An alternative would be
Integer toIntOrNull(String s) {
  return try { yield Integer.parseInt(s); }
    catch(NumberFormatException _) { yield null; };
}
Here, all kinds of refactorings are possible. And we actually don't need to express pattern matching, because we essentially don't need any pattern matching.

I’m not sure I follow the point of making try/catch an expression here.  We can write this code today with return:
Integer toIntOrNull(String s) {
    try { return Integer.parseInt(s); }
    catch(NumberFormatException _) { return null; }
}


Also, note that some of the situations which are usually solved with exception handling in modern Java (e.g. Pattern.compile -> PatternSyntaxException, or UUID.fromString -> IllegalArgumentException, or Integer.parseInt above) will be covered in future by member patterns. So probably if we concentrate more on member patterns, people will need much less exception handling in business logic, and such an enhancement will be not so useful anyway? Speaking about the sample from the JEP, can we imagine something like this in the future (sic!) Java?

switch(future) {
  case Future.cancelled() -> ...
  case Future.interrupted() -> ...
  case Future.failed(Exception ex) -> ... // no need to unwrap ExecutionException manually
  case Future.successful(Box b) -> ...
}

One more note about the JEP text. It's unclear for me whether 'case throw' branches could catch a residual result. More precisely, if MatchException happens, or NullPointerException happens (selector evaluated to null, but there's no 'case null'), can these exceptions be caught by the 'case throws' branches in the same switch?

I think if the selector evaluates to “null”, then it is the switch, not the selector, that throws NPE so I wouldn’t expect a case throws NPE to handle that exception.  Similarly, a MatchException isn’t thrown by the selector, it’s a result of the exhaustive switch not matching (ie: remainder handling) so I’d similarly expect it not to trigger a case throws MatchException.  But happy to be corrected here.

--Dan

With best regards,
Tagir Valeev.


On Fri, Apr 19, 2024 at 3:05 PM Angelos Bimpoudis <angelos.bimpoudis at oracle.com<mailto:angelos.bimpoudis at oracle.com>> wrote:
Dear spec experts,

A while ago we discussed on this list about enhancing the switch​ construct to
support case​ labels that match exceptions thrown during evaluation of the
selector expression. A draft JEP for this feature is now available at:

https://bugs.openjdk.org/browse/JDK-8323658

Please take a look at this new JEP and give us your feedback.

Thanks,
Aggelos
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-spec-observers/attachments/20240422/0dfc3edb/attachment-0001.htm>


More information about the amber-spec-observers mailing list