Would We Still Criticise Checked Exceptions, If Java had a Better try-catch Syntax?

In the context of a previous blog post about JUnit 5, Maaartinus, one of our readers, has brought up a very interesting idea:

The only problem with try-catch is its verbosity, which is something I can live with (IMHO a lone catch would do better, the implicit try would apply to all preceding code in the block; just syntactic sugar)

Huh!

Imagine a world where the following is valid Java code:

{
    something();
}
catch (Exception e) {
    /* All exceptions from the above block */
}

Likewise:

{
    something();
}
finally {
    /* Clean up after the previous block */
}

In other languages, this is implemented exactly as such. Take PL/SQL, for instance. An ordinary block looks like this:

BEGIN
  SOMETHING();
END;

Replace curly braces by BEGIN and END keywords, and you have exactly the same thing. Now, if SOMETHING raises an exception, in PL/SQL, we can append an EXCEPTION block, which does exactly the same thing as catch in Java:

BEGIN
  SOMETHING();
EXCEPTION
  WHEN OTHERS THEN NULL;
END;

Indeed, in these very trivial cases, the try keyword seems optional just like there is no such keyword in PL/SQL, and we don’t really need it as the scope of the catch and/or finally blocks is very well defined (at first sight, there might be caveats, of course).

So what? We’ve saved 3 characters…

In these trivial cases, we’re not gaining a lot from the “improved” syntax. But what about many other cases where the notoriously verbose try { ... } catch { ... } syntax might be getting on our nerves…? Again, in PL/SQL, whenever you’re using a block using BEGIN .. END, you can automatically profit from optionally adding an EXCEPTION block.

Without thinking this through thoroughly though (whew, some English language usage!), this could add immense syntactic value to Java. For instance:

lambdas

// Better:
Consumer<String> consumer = string -> {
    something();
}
catch (Exception e) {
    /* still part of the consumer */
}

// Instead of:
Consumer<String> consumer = string -> {
    try {
        something();
    }
    catch (Exception e) {
        /* still part of the consumer */
    }
}

Would that have prevented long discussions about checked exceptions in lambdas and in the Stream API

loops

// Better:
for (String string : strings) {
    something();
}
catch (Exception e) {
    /* still part of the loop's iteration */
}

// Instead of:
for (String string : strings) {
    try {
        something();
    }
    catch (Exception e) {
        /* still part of the loop's iteration */
    }
}

Again, tons of syntactic value here!

if / else

For consistency reasons, although this might appear a bit esoteric to people used to Java code. But let’s think out of the box, and admit the following!

// Better:
if (check) {
    something();
}
catch (Exception e) {
    /* still part of the if branch */
}
else {
    somethingElse();
}
catch (Exception e) {
    /* still part of the else branch */
}

// Instead of:
if (check) {
    try {
        something();
    }
    catch (Exception e) {
        /* still part of the if branch */
    }
}
else {
    try {
        something();
    }
    catch (Exception e) {
        /* still part of the else branch */
    }
}

Huh!

method bodies

Last but not least, method bodies would be the ultimate entities profiting from this additional syntax sugar. If you’re admitting that the curly braces in methods are nothing but mandatory blocks (or mandatory BEGIN .. END constructs), then you could have:

// Better:
public void method() {
    something();
}
catch (Exception e) {
    /* still part of the method body */
}

// Instead of:
public void method() {
    try {
        something();
    }
    catch (Exception e) {
        /* still part of the method body */
    }
}

This is particularly useful for (static) initialisers, where exception handling is always a pain, as there is no way to specify a throws clause in a (static) initialiser (might be a good opportunity to fix that!)

class Something {
    
    // Better:
    static {
        something();
    }
    catch (Exception e) {
        /* still part of the initialiser body */
    }

    // Instead of:
    static {
        try {
            something();
        }
        catch (Exception e) {
            /* still part of the initialiser body */
        }
    }
}

Take this one step further

Of course, we wouldn’t stop here. We’d also get rid of the very peculiar requirement of putting curly braces after catch (or finally). Once we have established the above, how about also allowing:

// Better:
something();
    catch (SQLException e)
        log.info(e);
    catch (IOException e)
        log.warn(e);
    finally
        close();

// Instead of:
try {
    something();
}
catch (SQLException e) {
    log.info(e);
}
catch (IOException e) {
    log.warn(e);
}
finally {
    close();
}

Now, make exception blocks expressions, rather than statements, and suddenly, Java starts to look an awful lot like all those cool languages. Like Scala. Or Kotlin.

Conclusion

Of course, the “old” syntax would still be possible. For instance, when using the try-with-resources statement, it is inevitable. But the big advantage of such syntax sugar is that in cases when we have to handle exceptions (namely checked exceptions), the pain would be lessened a bit, as we could do so without nesting blocks several levels deep. Perhaps, with this syntax, we would no longer criticise checked exceptions at all?

Very interesting ideas, thanks again, Maaartinus, for sharing.

What are your thoughts?