89 comments

[ 2.4 ms ] story [ 160 ms ] thread
IMO invisible control flow only carries its weight in fully expression-based languages.
..wat. Either rethrow it, deal with it, or rethrow it as unchecked. As a Java developer, you can make the choice. I really don't have any issues with libraries telling me something can go wrong. I'd rather know something can go wrong before putting my code in production than while it runs.
Not only that, the idea of checked exceptions actually appeared on CLU, Modula-3 and C++ before Java came into scene, but somehow only Java gets mentioned.

Nowadays I spend most of my time on other languages and while it is more ergonomic, I do spend more time reading documentation regarding possible errors.

There are no checked exceptions in C++. throws in C++ is very different from Java, even if syntax is similar.
Yes there are.

Just that instead of being a compiler error like in Java, the unexpected() handler is called; which by default calls terminate() to kill the application.

This behaviour has been removed in C++17.

However C++23 might introduce a similar syntax for value type exceptions, but following up semantics similar to how Swift does it.

You could declare throw() exceptions in C++. If you did that the runtime would intercept and typecheck any thrown exception against that list. Calling code could rely on that list since any not listed exception would cause the application to terminate. Modern C++ replaced that with noexcept - a function either can throw or it can't and template code can branch on that information.
I think the real problem is that the "rethrow as unchecked" case is probably the most common, but is clunky and verbose.

A more ergonomic way to do that would probably make people stop complaining so much.

This problem is especially noticeable when using lambdas. If you're implementing an interface that doesn't have a throws clause, but the body calls something that throws a checked exception, you _have_ to introduce a try/catch to your lambda body. You don't get to re-throw it without wrapping it in a RuntimeException.

Checked exceptions are not well thought concept and I'm surprised that they were introduced in Java. Java seems to be pragmatic language adopting best practices after they were fleshed out in other more experimental languages for decades. I consider checked exceptions to be semiofficially deprecated after they introduced java.io.UncheckedIOException in standard library. So it's like discussion is over, just use unchecked exceptions everywhere. Any sane library does that anyway.
They were following up on CLU, Modula-3 and C++ exceptions design, the predecessors that everyone forgets when complaining about them.
I don't know what Gosling's actual influences were, but when Java came out I was struck by its similarity in many respects to Modula-3. One convention I wish they had borrowed was modules instead of classes as the encapsulation technique, although I think many potential converts to the language would have found Modula-3's partial revelation of types to be confusing.

We live in a wonderfully polyglot world these days. At the time Java was introduced, I felt like new programmers encouraged each other to pick sides. Were you going to use C? Pascal? C++? (Visual) Basic? Ada? Lisp? Your "choice" brought you praise from one camp and ridicule from another.

Java started out as a fad language for smart devices, then pivoted into a fad language for applets, and only then adopted the image of a boring server-side language. You can see lots of weird stuff in Java's design, like the security model with classloaders, which is overengineered for server-side software but makes sense for smart devices and applets. So I'm not surprised to see experiments as part of Java.
As a counter point people have been trying to get the equivalent of checked exception in C++ for a while and there are well supported proposals on the table right now.

Checked exceptions require reannotating the call graph on a change, but that's not surprising: you would have yo do the same thing for changes in result types. Failure modes are no less part of the interface. For generic code you need to be able to abstract over exception types, it seems that Java lacks the capability.

An escape hatch to dynamic unannotated exceptions would still be useful: C++ will mantain that.

The trouble is that in most programs I want to catch most exceptions very near the top ("main" function), not in the calling function immediately above where the exception is thrown. In other words the check is useless and done in the wrong place.

Of course there are exceptions which are really return values that do need to be checked by the calling function, but that's a misuse of exceptions in languages which don't support lightweight qualified unions (such as those provided in ML/Haskell).

In any case I think Erlang's quite different approach to handling errors is far superior.

> The trouble is that in most programs I want to catch most exceptions very near the top ("main" function), not in the calling function immediately above where the exception is thrown. In other words the check is useless and done in the wrong place

It's been a while since I've done any Java, but I seem to remember that you can just annotate your function with the "throws" keyword to propagate exceptions up the call stack, so that you can handle them somewhere else.

It's also been a long time for me, but I believe you're correct that annotating functions with "throws Exception" makes the function unchecked. However the problem is still twofold: (1) You have to change the called function. (2) The default is wrong.
Yes you can, but the problem is that this means reannotating all the stack up to "main" every time some tiny implementation detail changes - or you use different JSON parser that has "JsonParsingException" and not "JsonParserException", and so on. Or, you have to wrap them all into generic/runtime exceptions, which negates the whole benefit.
I think checked exceptions are great. When using a library you instantly know what all bad could happen while calling a function and retry/handle on specific exceptions. For example, when reading a file if you get something like a FileNotFoundException, you can treat it as a fatal case or can simply move to process the next file. This provides a lot more clarity to code and resonates with the static type safety paradigm of java.
I think that a common criticisms is that in many cases, regardless of whether the exception is checked or unchecked, there is little the process will do but give up.

Because this is common and sensible, the code ends up with either many exception handlers that convert checked to unchecked or add the exception to the calling method signature which propagates the problem up that call stack

(comment deleted)
Checked exceptions are great in some cases. They are huge annoyance in others. The complaint is that the language does not provide tools to separate these cases - you can't just say "this method doesn't handle exceptions, pass it up" or "ok, now I want all exceptions to be handled, so tell me if I forgot something". It does not give the programmer control. Java has excellent facilities to make it happen - annotation, static analysis tools, extensive array of code checkers - but all this has to bow to the built-in mechanism which is crude and inflexible.
I think the solution he proposed is exactly the way to handle errors. A RepositoryException conveys anything something on a higher level in the application should know - there was some error retrieving data from the repository. Full stop.
> Moreover, the client doesn’t care if an IOException or a SqlException is thrown. He just wants to know, if something went wrong. Consequently the thrown exception should be implementation agnostic.

My Java is very rusty at this point. But it looks like both SQLException and IOException are derived from Exception, so you could just specify the interface as:

  UserRepository.getUsers(Path) throws Exception
No?

An alternative would be to create a RepositoryException class, and warp the IO-/SQL-Exeptions into an instance of RepositoryException.

In any case I don't see a flaw with specifying an Exception type in the interface. It tells the developer concisely: "This can go wrong, and you should plan for this". And it's clearly better than C-style return codes which are hard to interpret and easy to ignore.

In the specific case of IO, I don't think whether a method involves IO or not is an implementation detail that should be hidden. IO is very costly and has very different failure scenarios than memory accesses/computation. You should always know whether you are calling a function or a remote service.

Your alternative 'RepositoryException' is considered the better practice. I believe Bloch recommends it in Effective Java.
You could, but there are also tons of other Exceptions which you don't want the user of your method to explicitly handle because they are bugs in your program that should be handled globally or cause the program to restart.
I can't tell you how many times I've had to write code to open a file that I know will always exist and have to write an empty try-catch. Or had to add an unused throws clause because an interface specified it. Sometimes I've even had to throw a different type of exception than declared by an interface method and was left to either refactor tons of code to uselessly handle the new checked exception or throw a runtime one and catch it at the smaller number of callsites, or throw it as one of the declared checked exceptions that would not accurately describe the type of error to be handled. Anyone use Hibernate and see how many times an underlying SQLException got wrapped by a HibernateException?

For functional programming languages, I think it's a lot more convenient to not have checked exceptions because how they affect the composability of closures. Not saying it's impossible, but imagine having to add throws clauses to your .map and .filter chains.

> Sometimes I've even had to throw a different type of exception than declared by an interface method and was left to either refactor tons [...]

I see that this is inconvenient. This might be the essence of the whole thing here. It is way worse than adding a new argument to a function!

1. There is no way to overload a function with different Exception clauses.

2. You not only need to touch the call sites but also parts of the stack that led to the call sites.

> I've had to write code to open a file that I know will always exist

How can you ever be sure a file exists? I can be sure a function or a variable exists if I declared it. Files can be manipulated by other processes, USB drives are ejected, etc. You can never be sure they stay where it was. Even if I created the file one line above, I technically still have a potential race condition.

> For functional programming languages, I think it's a lot more convenient to not have checked exceptions.

There is a clear tradeoff here. E.g. when writing python or bash scripts I seldom care to catch errors. Let it crash fast and return a stack trace. For production grade software you usually want to much more careful and handle exceptions explicitly. Checked exceptions are clearly targeted for the latter.

> How can you ever be sure a file exists?

Well I can't ever be sure, but the example I'm thinking of is looking up a config file on the file system that was copied there by a deployment script. If it didn't exist, a RuntimeException failing the application would be entirely appropriate.

If I was checking for a file on a USB drive or some other source, I'd very much have a try-catch. Do I need checked exceptions in this case? Still debatable in my opinion.

WRT checked exceptions targeted for production grade software, curious what you think about the (lack of) checked exception handling in Java 8 lambdas.

> I can't tell you how many times I've had to write code to open a file that I know will always exist and have to write an empty try-catch.

How do you know that the file will always exist? I'm pretty sure you don't. The try catch shouldn't be empty. Now it's going to crash when you try reading or writing from the file so all you did was make it so it blows up with a less helpful error later.

That's an entirely appropriate failure mode for my case. If my app can't find an important file, then it should fail immediately, alerts then go off, dashboards are bleeding, ops is getting paged, etc.
You did not get that failure mode. If you want to fail at file not found, wrap it in a RuntimeException or IllegalStateException or something. You don't even have to create a new class, just use one of these 2 built ito Java.

For example: you store a conversion factor in you config file (eurostodollars=1.12 or whatever).

If you have an empty catch block, you now have a null Reader object or something. Next read will throw an IOException, which you also swallow and you now have an uninitialized config value. Except that one will be zero instead of null.

500 km further in your code, a month after you started the app, that zero gets used in your code.

  amounttopay+=eurostodollars*euroamount;
This will not fail immediately either. No, it will do something weird. In the best case, you crash for a really weird reason. More probably, you send out a file or web call or ... with insanely wrong data. Bonus points if you sign it with a private key, then you're legally bound by whatever nonsense you happened to promise. The next program in the chain might sit either in your company or not. It will swallow your garbage date wholly, do no checking of its own as this no-error-checking culture is pervasive. It will either crash and or do further damage. When the shit finally hits the fan, it will be a containerload of shit instead of a mouse dropping.

I saw this kind of scenario again and again. No , the app cant possibly start without database, that never happens, and anyway the next user will notice the empty database. Except after power failure in the middle of the night, when the database is still booting up while your app is reading the config from it. When business hour start, everything seems fine. Until it isnt.

Nothing about checked exceptions inherently prevents the failure scenarios you list, particularly the one about the power failure.
Also, I gave a bad example for empty try catch. In this case, I would want a runtime exception thrown. Apologies for misleading the conversation.
In that case, you avoided disaster and I'm starting to agree with you.
Correct, this was about swallowing errors instead of rethrowing/handling.

Checked exceptions are good if you want to be sure your caller does something when things go south. Some examples from my experience:

* Most things that should be told to the end user, i.e. Business Exceptions. E.g. a webshop user tries to buy something but your stock has been emptied why he was deciding if he should buy or not.

* Follow-up after a modification: If you wrote half a file, you can delete it in your catch IOException instead of letting it linger around. My company has a tendency to pick up and process any file found in an output directory as soon as you release the file lock. But just not filling your disk is good enough for me as reason.

if the problem is that the file is missing, FileNotFoundException sounds like the most useful one
an empty try catch wont necessary make that happen. It could countinue with the assumption that it has a File when it doesnt, corrupting further things in its (uncontrolled) way out.
Yes that's a bad example for an empty try catch, in that case one should throw a runtime exception. Apologies for the misleading example.
That's the gig economy perspective. What happens when you are deploying software on someone else's computers? There are often no "ops" and no "dashboards" but a single technically unskilled user trying to get some work done.

Don't get me wrong, I also think that Java's checked exceptions are not really useful but your argument is very one-sided.

I don’t get it. Even if you think a file can always exist (bit of an amateur assumption, btw), then why would you wrap it with try/catch at all?
> and have to write an empty try-catch

I end up converting checked exceptions to runtime exceptions all the time:

    try {
        ...
    } catch (RuntimeException e) {
        throw e;
    } catch (Exception e) {
        throw new RuntimeException(e.getMessage(), e);
    }
A checked exception is a way for a library to indicate "this is something that can happen and it's up to you to deal with it". It's compiler enforcement so that your user doesn't see "Exception thrown! <Stacktrace>".

And I think that's a very good thing.

The author, to me, misses the point. Don't rethrow the same exception. Handle it. It you want to handle it by throwing another internal unchecked exception, that's fine- it's certainly one way to handle it. But just modifying your signature so that you don't have to do the work of handling the situation is lazy and a great way to cause yourself bugs.

It leads to some top-of-the-stacktrace method having "catch(Exception e)" where there's little chance you actually know why this exception is happening, what lead to it, what it's about. Your users deserve better than that.

The alternative is that the developer doesn't know all the circumstances they must handle. They may not see a file not found error for years in production. Suddenly an unexpected exception of throwing up and we have to dig through user error reports and stack traces to figure out what happened.

(comment deleted)
I think the problem is that checked exceptions seem to defeat the benefits of using exceptions.

You're better off returning something like Rust's Result type.

They are completely equivalent.
They aren't, because you have to use clunky verbose syntax (and try/catch blocks aren't expressions, which makes things worse). Also, there are several limitations on generics with exceptions that don't exist with regular values.

My point is that there's no reason to include special syntax/concepts for checked exceptions over just using values.

I quite prefer the Option monad approach in Scala where you can pattern match the result. Far more succinct and doesn't require program logic flow to be interrupted.
(comment deleted)
> Don't rethrow the same exception. Handle it. It you want to handle it by throwing another internal unchecked exception, that's fine-

No, that's not fine. Typically, the layer immediately wrapping the throwing code has no knowledge about semantics. It's measurement code, logging code, caching code, transaction handling code, batch explode/aggregation code etc, but nothing that could make sense of a FileNotFoundException. The layer that wraps all this infrastructure code has more info, and can make sense of individual exceptions. By wrapping the original exception, this code now has to go on a getCause() safari to find out what happened. And yes, with checked exceptions there is no way around that, and that's the reason they should go.

I don't mean to wrap it in a generic exception, I mean to wrap it in a context-specific exception written for the purpose. A "UserDoesntExistException", or a "DatabaseNotRespondingException". Something for the purpose that likely exists in a useful hierarchy.
This is better, but it could have been the original exception all along, given the rare number of cases in real-world code where the call site actually knows how to recover.
Typically you don't need to catch at this level of granularity. A generic UnavailableException would cover it. The reasons why a method can fail vary by implementation and you typically don't need to expose this at the API level. It shouldn't be a breaking change for a method to fail for a different reason because the implementation changed.

If you take this approach, there are two kinds of methods: those that always succeed and those that can fail. It's similar to how Go's error type works.

Java is a mess not due to the existence of checked exceptions, but because they created an unfortunate exception hierarchy and that resulted in inconsistent API design where there's no consensus about what to do.

This is exactly the opposite recommendation I give.

Never handle an exception (always rethrow) unless you know that you can do something specific in that exceptional case. In most cases just rethrowing the exception is the correct thing, and many bugs come from people mishandling exceptions.

A perfect example is InterruptedException, where really bad things happen if you don’t rethrow that.

Just rethrow, it’s generally the best thing.

I think you both generally agree. The choice is either handle now, or rethrow and handle later.

Which one is "default" depends on if you're writing library code, application code, or service code. Especially in services rethrow, catch all at the top, and log is common and makes sense.

Probably less appropriate for a zlib or a file not found call. You'll have to do something (unless the answer is crash ... Which it is a sometimes!)

Ultimately, the thing I most disagree with in the GP is the idea of handling and rewrapping. There a many cases where this would be bad practice.
It just depends I suppose? Sometimes you rewrap to get an unchecked runtime exception out of it. Sometimes there's context worth adding: new IndexLoadException(ioException, "Is Hadoop down?").

Either way I think the one thing I hate hate hate is, losing stacktraces! Don't throw a new exception without wrapping the old one. I need that info!

> Either way I think the one thing I hate hate hate is, losing stacktraces! Don't throw a new exception without wrapping the old one. I need that info!

This! Throw as many RuntimeExceptions as you like, so long as you include the originating exception.

Again I go back to InterruptedException, this advice is very wrong in that context, and similarly others.

People often make the very big mistake of generically catching Throwable and then rewrapping that, which would include all Error types. Those should must be rethrown without wrapping. I don’t think you meant to imply that, but it’s easy to overapply advice like this.

The trouble is they don't scale and are generally just a pain. Especially with how bad they play with lambda expressions.
It really comes down to two things:

1. How exceptional are exceptions?

2. Are committed to providing good language support?

If exceptions are the result of programmer error or terrible malfunction, then unchecked exceptions are fine. But in Java, exceptions are fairly common, even for common low-level stuff: read a non-existent file, parse a non-numeric string, look up a non-existent domain name. If you're going with that approach, it makes sense to type and control these.

As for language support...Java is interesting. It doesn't have unions in any of its types except for exceptions. But that makes it interesting trying to write generics, e.g. in a very functional approach. In my experience, most functional Java paradigms just don't support any user of checked exceptions and you make all exceptions unchecked.

Thing is - the person who wrote that code doesn't know what I'm doing with it - a recent offending library for me was Jackson throwing an IOException if it can't parse the JSON.

Except I knew that it would always be receiving valid JSON because this code was downstream of a Kafka Streaming app whose entire purpose was to validate that the record was valid JSON that conformed to a JSON schema.

But the authors of Jackson require me to add a useless catch clause to my code. And seriously, an IOException for unparseable JSON? At least throw an UnparseableJsonException or something.

I will write catch clauses for exceptions that could reasonably be expected to occur and are recoverable from - but if an exception does occur, and you can't recover from it, what the hell is the point of a mandatory catch, log, and rethrow?

In my experience, 99% of the handling of a checked exception is to catch them, and rethrow in RuntimeException, which shows that the intent doesn't match the reality of how people are programming.

But, that said, I'm writing code on the JVM for our own internal apps - we're never going to have a desktop user facing a stack-trace. I think that when Java was developed it was intended to be far more user-facing than it is now.

I agree that checked exceptions enforce clarity however I absolutely disagree on the fact that the exception should be managed immediatly. Usually exceptions can and must be managed at different levels of the call stack depending on their origin and destination. My experience tells that usually in an application there are just a few sinks that can be used to ultimately manage exceptions raised from different application flows. The pro of checked exception is to enforce exception management the cons is that they require a pedantic management. For unchecked exceptions pros and cons are exactly inverted. What would be ideal is to have a meta-exception management rule system that would allow to specify which exceptions are allowed to pop-up and which must be managed at the class and method level. Is there any language that provides something nearly similar to this?
Checked exceptions make a lot of sense if they are business logic related exceptions for example - CustomerNotFoundException, WithdrawUnauthorizedException. The client knows that server will throw these exceptions and can take different actions accordingly (for example displaying an appropriate message rather than a generic error message). If it is just one general exception then you are clubbing several different situations in just one bucket.
My rule of thumb: use checked exceptions if and only if your exceptions are not supposed to be rethrown and must be handled by the caller directly. These cases are very rare, but I recently had one them.
I think that's a reasonable rule of thumb, and it tends to be the way I use checked exceptions as well.
In general it's hard to distinguish between a good idea executed badly and a bad idea executed badly.

I think Java executed badly here.

There doesn't seem to have been a clear policy in the standard library for what kind of exception should be checked, and there really ought to be a concise way to say "panic and abort the process" when that's appropriate.

Also, when you have checked exceptions (or similar things like error returns which it's compulsory to check), sometimes you need to add variants to library functions for cases which can't fail. For example, you might have a library function which takes a character-encoding parameter and can't fail if that encoding is UTF-8; you might be better off adding foo_utf8(x) which is like foo(x, UTF_8) except for the error specification.

Agreed. I see checked exceptions as kinda parallel to Rusts Result type (and its older cousins), which people seem to like.
> there really ought to be a concise way to say "panic and abort the process" when that's appropriate.

There is, it is the Error class and its subclasses, e.g. OutOfMemoryError

I mean a bit more concise than that.

To catch and rethrow an exception you still need a try and a block, and you probably add a level of indent.

Compare Rust, where you can generally spell "I'm sufficiently confident that this won't happen that I'm happy to abort if it does" as « .unwrap() ». (That's not a great word for this idea, but it is at least short.)

I think checked exceptions are a bad way of doing Result types (Result in Rust, Either in Haskell), and that those are generally better than unchecked exceptions. In my opinion, unchecked exceptions aren't that much different than checked exceptions. The difference is that you have to get them right at the level of documentation, not type signatures, and documentation usually doesn't have a compiler that will help you.

However, back in the Java 7 days, I think checked exceptions would've been made a lot better with a bit of syntactic sugar:

    public void foo() rethrows* {
         bar();
    }
Where the meaning of the rethrows term is that the method throws whatever exceptions the methods it calls throws. The bottom level methods that throw exceptions would still have

This would make it easier to refactor code to change exception behavior. It would only be at the public API boundaries that you have to wrap exceptions to avoid changing the API or modify your method signatures.

Now that lambdas are here, checked exceptions are even more painful, so I don't think this solution would tip the balance. But for the language that we had, I think it would've helped a lot.

* rethrows might not be the best name. I'm more attached to the idea than the term.

I think the authors has some good points, but I don't think the proposed solutions really solve the underlying problem.

In particular:

> But how do I know, that a method throws an unchecked exception? The compiler doesn’t tell me. Yes, you are responsible to document unchecked exceptions carefully.

Except if you try that, the same problem that makes checked exception problematic will bite you here too - except now it's easier to blame yourself than blaming the compiler.

If you write

  void foo() throws FooException
all you can really say is "It might be useful to consider FooExceptions when calling this function".

- You can't guarantee to a caller that FooException is always important to consider - because, as the author correctly stated, there might be situations where you call foo() but it's formally impossible that a FooException is raised.

- You can't guarantee that FooException is the only important exception to consider either, because foo() might call other functions that might raise different exceptions on their own - a fact that you could probably read up in those other functions' documentations, if you knew you had to deal with those other functions in the first place.

This limitation stays whether FooException is checked or not.

In then end, what you'd like to know when calling a function is the complete set of exceptions this call site could produce at runtime. Checked exceptions were a an attempt to answer this question, even if it was a very clumsy and eventually unworkable one.

Maybe a better solution would be more sophisticated static analysis, which could try to answer this question as part of an IDE. However, this would require that the analysis tool get complete information about all abstract functions calls, interceptors and other things that could happen as part of a call. This seems somewhat doable with fully static languages like Go but seems to run completely against Java's philosophy of being extensible at runtime.

Having programmed extensively in Java, C# and C++ I can say with confidence that checked exceptions is the most important thing missing in C++ and C#. Around the time C# was released there was a lot of debate around the topic of checked vs unchecked exceptions and this created an impression that Java's use of checked exceptions was "controversial". In fact it is a feature every modern language should have.

Exceptions that can be thrown by a method should be part of the contract of that method. Changing the list of the list of exceptions that can be thrown by a method is just like changing the parameters of the method. This is something that should cause calling code to fail to compile. This will allow you to inspect the calling code and decode how to deal with the new exception.

When the exception that can be thrown by a method is not known you don't know what exceptions to catch and what exceptions to pass through to callers. You can't rely on documentation because the documentation is not verified be the compiler and so is not reliable. All you can do is to run the program a few times, see what exceptions you get and handle them. Even if you are able to determine the full list of exceptions using this strategy, a future revision of the called method can throw a new exception and cause your program to crash. As a result, in large C# code bases it is common to see catching the base Exception class because this is the only way to prevent a crash. This causes exceptions to be "swallowed" because higher level code that is actually prepared to handle certain exceptions never get the exception.

With Java this problem doesn't exist. When you call a function you know exactly what exceptions can be thrown and you can catch (or pass through) exactly those exceptions. There is no need to catch the base exception class.

Here's an example C# pseudocode to illustrate the problem with not having checked exceptions:

Programmer A writes this C# code:

  class A {
     public static void startFoo() {
        if (/* Foo is not installed */)
            throw new FooNotInstalled();
        // ...
     }
  }
Programmer B calls the above code like this:

  class B {
     try {
        A.startFoo();
     }
     catch (FooNotInstalled) {
        // Tell user to purchase Foo
     }
  }
Later programmer A updates his code because there are newer versions of Foo and he needs the newest version:

  class A {
     public static void startFoo() {
        if (/* Foo is not installed */)
            throw new FooNotInstalled();
        if (/* Foo version is too old */)
            throw new FooVersionTooOld();
        // ...
     }
  }
Now the code written by Programmer B crashes even though it compiles fine. That's bad.

Had this been Java, programmer would be would be alerted to the fact that he needs to decide what do do if the version of Foo is too old. This is good.

So listing exceptions that can be thrown is a good thing because it helps you write more reliable code. If you are lazy you can always defeat the system by declaring your method as throwing a parent (or the root) exception class, in which case it is no worse than C#.

Agreed that there are some benefits to having exceptions as part of the interface, but there are ergonomic challenges around being annoyed with a bunch of breaking changes in spots where you really don't want to be hassled with the details. I see it causing more problems than it solves. When people are forced to handle exceptions, they often handle them badly. IMO, an immediate program crash with a nice clear stack trace due to an unhandled exception is almost always preferable to a festering, difficult-to-trace (or even detect) bug due to an error condition that got swept under the carpet.

You're very likely to get some of the latter sneaking in with an interface change like the above. The person who made the change is going to be the one who gets stuck getting the code to compile. If they are faced with tens (maybe even hundreds) of compile errors, they're going to be forced to make a whole bunch of unplanned changes to a bunch of code they didn't write and might poorly understand even before they can get to the point of being able to start running the unit tests.

> So listing exceptions that can be thrown is a good thing because it helps you write more reliable code.

This is entirely valid Java:

    public class FooBob {
    
        class ExampleException extends RuntimeException {
        
        }
    
        public boolean foo(Boolean bob) throws ExampleException {
            ...
        }
    }
You don't need to throw a checked exception to document that your public API throws an exception.

You're right, that throwing a new checked exception would force programmer B to handle it, but frankly, I'd rather that programmer B a) versioned his dependencies or b) read the source code of his dependencies.

Also, not sure about C# land, but in Java land, if you're writing libraries, we have tools to enforce semantic versioning based on public API changes (the Maven enforcer plugin with the semver plugin) which would also communicate breaking API changes.

I would much prefer that enforcing the communication of backwards-incompatible API changes not be done by the compiler.

Exceptions are for error reporting. Pass them through all the way to the top, and log them. Enforce module preconditions proactively.

    if(!isInstalled(Foo)) {
      purchase(Foo)
    } else {
      start(Foo)
    }
This! Java is hugely overusing exceptions for reporting situations that are completely normal (probably because it didn't have anything like Option type or any way to return error results). Exception should be for "somebody stole our HD and we can't read data" or "database have died and we can't continue, all panic!", not for "user made a typo and we need to display an error message". Using exceptions for routine flow control is bad design.
The problem is what would happen in reality is that programmer B would do catch(Exception e) { log(e); continue; } somewhere, because otherwise it doesn't work or requires to change signatures to 20 methods, and all good intentions will die with it. Yes, I've seen libraries (and not just something a random kid put on github, ones which are very mainstream and recommended for their use case) with APIs just plain declaring "throws Exception" because who cares, right? We warned you we might throw exceptions, deal with it! But why go to libraries - in Java's own stdlib AutoCloseable.close() is declared "throws Exception"! Tell me again about nice exception hierarchy and versioning and stuff.

And of course writing "throws Exception" on every single test is hugely annoying. Yes, I do not want to handle exceptions in the tests, that's the whole point of the test!

That whole discussion would not exist if java had a convenience syntax for rethrowing checked exceptions wrapped in a RuntimeException, in less than the customary four extra lines.

Some kind of call-site use of the throws keyword perhaps?

byte[] bytes = "Example".getBytes("UTF-8") throws RuntimeException;

I can't really think of a reasonable use case for anything other than RuntimeException, but in the old days, some people would surely have loved to rethrow wrapped as MyCompanyException.

It's fairly easy to get something like this with Java 8. I have my own set of wrapper functions that let me do something like:

  byte[] bytes = unchecked(() -> "Example".getBytes("UTF-8"));
Yes, unless they are used for business logic where certain outcomes must be accounted for.

Other than that, they're randomly scattered around the oldest parts of various apis and mostly ignored.

Also, we also discovered in recent years it's likely best to handle exceptions at the near the top, not the bottom by incorporate a TLEH (top-level error handler).
Did Java for many years. Their exceptions were a good next step when compared to C++, but there are many problems. For example, InterruptedException. It really should be a runtime exception, except that it is checked. I've very rarely seen code that handles this exception properly, and quite often seen it buried, causing all kinds of mischief at runtime.

Since Java, did Ruby (several years ago). It was a revelation that using essentially runtime exceptions was not as big a cluster as Java community at the time thought it would be. Convinced me that if you're going to have exceptions, they should be runtime exceptions.

Since then, been doing golang and really liking it. The verbose error handling is sometimes tedious, but is the least evil way of handling things that I've worked with. Our experience has been that the quality of code improves when error handling is tied in with the code flow instead of dealt with as a separate path.

This article is far too focused on the domain of web programming where you almost always propagate the error to the main loop. However, in a large amount of programs you actually are able to handle the error at the call site.

Furthermore, whether an error can be handled or not is a property of the caller, not the callee. If you're writing a public api and don't know your caller then checked exceptions are safer, and callers who just want to propagate the error can still write an api wrapper that rethrows them unchecked.

I do agree that checked exceptions are poorly implemented in Java though. In my dream world checked exceptions would be replaced by Either types which wrap a value or an error and there would be macro-methods for both propagating them and throwing them unchecked. There would also be union types for representing a set of possible named exceptions. Rust is probably the language closest to this.

Finally, if you're writing internal code for something like a web api, then of course you should stay far away from checked exceptions and rethrow them unchecked as early as possible when calling external code.

Whew, this is a hairy topic. The go camp has the same conversation around returning error codes.

I think checked exceptions are clunky, irritating, but all-in-all fairly good solution. I'd rather be forced to check an exception than to bit know about it at the very least.

I think Java's situation could be improved with 2 very minor changes if we had a time machine.

1) Make throwing any exception an unchecked "RuntimeException". The point is just to remove the type-based duality. Functions that declare "throws" still need to be checked.

2) A cleaner syntax to rethrow "try { } rethrow;" maybe. This won't force your function to declare anything. That's always a personal choice.

These could address the core issues, but still require a little education around best practices. If I had to distill them I'd put it this way:

There's no shame in just rethrowing unchecked through the layers of your own code unchecked or crashing a thread or request if you planned it, BUT if an exception is leaving "your code" and called by "someone else" make it checked. Preferably use an appropriate type.

Example: I'm writing an encoding library on top of an HDFS client. My functions should declare FileNotFound or IOException even though the came from the client "below" me because I want to be a good citizen to the code "above" me. Internally, my code doesn't need 10 thousand checked functions though.

I absolutely loathe whenever I come across an IOException. I _know_ that something can go wrong, everything will go wrong at some point, so my code needs to crash appropriately, everywhere. Nobody has to tell me about that, so no need for IOException or a MyLibraryException checked exception subtree.

There are a few specific cases where I might recover, and it's absolutely okay to check the documentation for them.

What? Do you think it's the libraries job to decide that the program should crash or yours?

I assume you agree that they should at least declare that they throw them? I agree that the rethrow should be painless at least.

I'm not sure I follow the first paragraph, but what I'm trying to say is that the JVM can, at any point, present an unchecked exception, or an error. In that case, at the very least, all resources have to be released, and nothing should possibly be left in an invalid state. All this while the stack is unwinding.

It's unnecessary to communicate that some call might fail. It's even misleading: a novice might handle a checked exception and rollback some state, but not think of all the implicit exceptions that might occur, e.g. a stack overflow. I'm not even talking about subsystems being replaced by distributed versions (so now there are networking errors too) or similar issues arising from composability.

So resource allocation and state logic should be written in a way that assumes everything can crash and burn at any time. And then one can sprinkle recovery and retry logic on top of it, but only in very specific cases, and on specific layers of the application.

The biggest WTF for me was that checked exceptions are not compatible with lambdas. So any concise elegant code that Java 8+ allows you to write, with all new streams API and all that goodness, does not work the second you need to use any class that has checked exceptions mentioned. I couldn't believe it when I first learned about it - but it's true: https://stackoverflow.com/q/27644361/214196 I still can't believe anybody thought it's a good idea to do it this way.