<div dir="ltr"><div class="gmail_default" style="font-family:monospace">Hello Amber Dev Team,<br><br>Here are some thoughts I had about Checked Exceptions, plus an idea.<br><br>I actually like Checked Exceptions. I think that, when used correctly, they enable an easy to read style of programming that separates the mess from the happy path.<br><br>I think Checked Exceptions are at their best when only one method of a try block can throw a specific exception. Meaning, there is no overlap between the Checked Exceptions of methodA and methodB. This is great because, then, you can wrap all "Throwable" methods in a single try block, and then each catch has a 1-to-1 mapping with the code that can throw it.<br><br>Conversely, Checked Exceptions are at their most inconvenient when multiple, consecutive methods can throw the same Checked Exceptions, AND WE WANT TO HANDLE THOSE SAME EXCEPTIONS DIFFERENTLY ACROSS THESE CONSECUTIVE METHODS. In this case, your only real recourse is to handle each task individually with a separate try catch block.<br><br>For example - let's say I want to make a new folder, create a file in that folder, and then write content to the newly created file. That seems like a reasonable amount of work for a single method.<br><br>For creating the new folder, we have Files.createDirectories() [1]. It throws the Checked Exception FileAlreadyExistsException. <br><br>For creating the file and writing content to the newly created file, we have Files.write() [2]. It too throws FileAlreadyExistsException.<br><br>Now, what if I want to handle the exceptions differently? The simplest use case would be -- to throw a better error message to the user.<br><br>```java<br>private Path save(Path parentFolder, byte[] contentToWrite)<br>{<br><br>   try<br>   {<br>             Files.createDirectories(...);<br> }<br>     <br>      catch (FileAlreadyExistsException e)<br>  {<br>             throw new IllegalStateException("helpful error message 1", e);<br>      }<br>     <br>      try<br>   {<br>             return Files.write(...);<br>      }<br>     <br>      catch (FileAlreadyExistsException e)<br>  {<br>             throw new IllegalStateException("helpful error message 2", e);<br>      }<br><br>}<br>```<br><br>As a side observation, statement lambdas vs expression lambdas have given me this mental model that blocks are for multiple lines of code while expressions are for one. I know several discussions have been had on try-expressions and whatnot, and I agree that they aren't a good fit. Regardless, having a single method in the try block makes me feel like the noise-to-value ratio is a little high. I can sort of accept it for the catch block, but for try? Annoying.<br><br>The side observation is relevant, but going back to the main point -- because I want to handle both cases differently, I must make 2 try catch blocks. I think this is at least one of the reasons why some developers dislike Checked Exceptions.<br><br>Now, the obvious solution is to remove the ambiguity, one way or another. There are a couple of ways to do this.<br><br>One way is to create a wrapper method that catches and throws a more specific checked exception. Instead of Files.createDirectories(), I create my own Utils.createDirectories() that throws CantCreateDirectoryBecauseFileAlreadyExistsException. Then, I can just catch that specific exception and handle it as expected.<br><br>But this means writing a whole bunch of utility style methods to work around a lack of specificity that can only be achieved by wrapping individual lines of code in blocks. I will hereby call them micro-blocks. Ignoring the fact that the utility methods just clog up my codebase, they also tend to be easy to misplace or I accidentally make duplicates of them without meaning to. In short, its a whole bunch of low-value code that is easy to forget and only exists to avoid some friction.<br><br>There are a few other ways, but they involve either writing something resembling micro-blocks, or more indirection, like with the utility methods.<br><br>Here's my pie-in-the-sky idea. I don't care about syntax. But for now, I will call it Tagged Statements and Tagged Exceptions.<br><br>```java<br>private Path save(Path parentFolder, byte[] contentToWrite)<br>{<br><br>        try<br>   {<br>             #folder Files.createDirectories(...);<br>         #file   return Files.write(...);<br>     }<br>     catch (#folder FileAlreadyExistsException e)<br>  {<br>             throw new IllegalStateException("helpful error message 1", e);<br>      }<br>     catch (#file   FileAlreadyExistsException e)<br> {<br>             throw new IllegalStateException("helpful error message 2", e);<br>      }<br><br>}<br>```<br><br>Doing it this way, all ambiguity is gone, while boiling things down to only the code that needs to be there. Plus, this also gives us the benefit of using the code we have (already written).<br><br>The semantics are simple.<br><br>* All statements in a method body can be prefixed by a tag -- called a tagged statement.<br><br>    * #, followed by an identifier, followed by whitespace, followed by the statement to be tagged.<br><br>* All exceptions thrown by the tagged statement can be referenced in catch parameters via a tagged Exception -- an ExceptionType prefixed by the same # identifier.<br><br>    * #, followed by an identifier, followed by whitespace, followed by the ExceptionType to be tagged.<br><br>* You can't put the # identifier in the middle of a statement (System.out.println(#1 someMethod()) <---- invalid).<br><br>And the best part is, this blends in nicely with existing semantics. If you have a catch block with no tagged catch parameters, then it works the way that it always has. But if you want to specify, then use a tagged exception. If you want to handle multiple types of exceptions using the "|" symbol, that logic works exactly for tagged exceptions too. You can even mix and match them. Again, I don't care about syntax. I care about the fact that this is something you can do at the call site ad-hoc.<br><br>The part that I like the most about this is that it actually makes try-catch way more attractive. Obviously, if I am trying to do control flow, then try catch is still not the right vehicle (and if I still must, then it should really be handled in its own try catch block or a separate method). But now, all those errors that I didn't really want to specify or build around becomes really easy to do. I just add an inline signifier, then a matching catch block. The only hit to readability is the prefix. You can make it verbose if you like (#recoverable) or terse (#1).<br><br>As a potential bonus, it might be a good idea to allow several different statements to have the same # prefix. Meaning, methodA, methodC, and methodE all have #1, but methodB and methodD have #2. I am indifferent to this, and I am fine leaving it out.<br><br>Another benefit is that it allows you to handle all Exceptions from that particular join point the same. Let's say there is a method call in your method that all failures it has can be handled the same. Simply attach a prefix to it (#blah) and then make a catch (#blah Exception e) or something similar.<br><br>I would also add a warning if a method has a tagged statement that is not explicitly referenced by a catch block. Catch parameters must spell out the tag explicitly to count as an explicit reference.<br><br>Now, this solution doesn't solve the "bigger" problems (some would say) with Checked Exceptions (Streams/Lambdas + Checked Exceptions). But I think it makes it makes Checked Exceptions and try catch blocks (both good things that we should be making better use of) extremely ergonomic and easy to handle.<br><br>Thoughts?<br><br>Thank you for your time!<br>David Alayachew<br><br>[1]=<a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/file/Files.html#createDirectories(java.nio.file.Path,java.nio.file.attribute.FileAttribute..">https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/file/Files.html#createDirectories(java.nio.file.Path,java.nio.file.attribute.FileAttribute..</a>.)<br>[2]=<a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/file/Files.html#write(java.nio.file.Path,byte%5B%5D,java.nio.file.OpenOption..">https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/file/Files.html#write(java.nio.file.Path,byte%5B%5D,java.nio.file.OpenOption..</a>.)<br></div></div>