Java introduced the notion of checked exceptions, which are special classes of exceptions. In Java, a checked exception specifically is any Exception that does not extend RuntimeException. The checked exceptions that a method may raise must be part of the method's
signature. For instance, if a method might throw a java.io.IOException, it must declare this fact explicitly in its method signature. Failure to do so raises a compile-time error. This would be declared like so (also with java.util.zip.DataFormatException): import java.io.IOException; import java.util.zip.DataFormatException; // Indicates that IOException and DataFormatException may be thrown public void operateOnFile(File f) throws IOException, DataFormatException { // ... } According to Hanspeter Mössenböck, checked exceptions are less convenient but more robust. Checked exceptions can, at
compile time, reduce the incidence of unhandled exceptions surfacing at
runtime in a given application. Kiniry writes that "As any Java programmer knows, the volume of
try catch code in a typical Java application is sometimes larger than the comparable code necessary for explicit formal parameter and return value checking in other languages that do not have checked exceptions. In fact, the general consensus among in-the-trenches Java programmers is that dealing with checked exceptions is nearly as unpleasant a task as writing documentation. Thus, many programmers report that they “resent” checked exceptions.". For example,
C# does not require or allow declaration of any exception specifications, with the following posted by Eric Gunnerson: • Versioning: A method may be declared to throw exceptions X and Y. In a later version of the code, one cannot throw exception Z from the method, because it would make the new code incompatible with the earlier uses. Checked exceptions require the method's callers to either add Z to their throws clause or handle the exception. Alternately, Z may be misrepresented as an X or a Y. • Scalability: In a hierarchical design, each systems may have several subsystems. Each subsystem may throw several exceptions. Each parent system must deal with the exceptions of all subsystems below it, resulting in an exponential number of exceptions to be dealt with. Checked exceptions require all of these exceptions to be dealt with explicitly. To work around these, Hejlsberg says programmers resort to circumventing the feature by using a declaration. Another circumvention is to use a try { ... } catch (Exception e) { ... } (or even a try { ... } catch (Throwable t) { ... } ) handler. The Java Tutorials discourage catch-all exception handling as it may catch exceptions "for which the handler was not intended". Still another discouraged circumvention is to make all exceptions subclass , thus making the exception unchecked. An encouraged solution is to use a catch-all handler or throws clause but with a specific
superclass of all potentially thrown exceptions rather than the general superclass . Another encouraged solution is to define and declare exception types that are suitable for the level of abstraction of the called method and map lower level exceptions to these types by using
exception chaining.
Kotlin does not have checked exceptions, and even throwing a Java checked exception from Kotlin will not force the client to handle it from Java. However, this can be enabled using the @Throws annotation, which will emit the throws metadata to the JVM. @Throws(IOException::class) fun readFile() { // ... throw IOException("Failed to read file!") } // translates into // public static void readFile() throws IOException; // in the JVM
Similar mechanisms The roots of checked exceptions go back to the
CLU programming language's notion of exception specification. Later,
Modula-3 had a similar feature. These features don't include the compile time checking that is central in the concept of checked exceptions. Early versions of the C++ programming language included an optional mechanism similar to checked exceptions, called
exception specifications. By default any function could throw any exception, but this could be limited by a clause (similar to the clause in Java) added to the function signature, that specified which exceptions the function may throw. For example, this code was valid
C++03: • include using std::domain_error; using std::invalid_argument; // this could be similar to the Java signature // void performSomeOperation(int a, int b) throws InvalidArgumentException, ArithmeticException; void performSomeOperation(int a, int b) throw(invalid_argument, domain_error) { // ... } C++ throw clauses could specify any number of any types, even primitives and classes that did not extend std::exception (because C++ supports throwing objects of any type). If no type was named in the throw clause, it would indicate that the function would not throw at all. Exception specifications were not enforced at compile-time. Violations resulted in the global function being called. An empty exception specification could be given, which indicated that the function will throw no exception. This was not made the default when exception handling was added to the language because it would have required too much modification of existing code, would have impeded interaction with code written in other languages, and would have tempted programmers into writing too many handlers at the local level. This use of exception specifications was included in
C++98 and
C++03,
deprecated in the 2012 C++ language standard (
C++11), and was removed from the language in
C++17. Throws clauses were replaced by clauses. A function that will not throw any exceptions would now be denoted by the keyword, and instead specified that a function will throw. Although throw clauses are removed from the language, writing only throw() in the signature is legal and is equivalent to noexcept (no exception specified by the throw clause denotes that it cannot throw), however this is still considered deprecated. For transitioning a
codebase that uses throw clauses, the removed throw can be redefined as a macro. This is only a temporary fix to allow the code to compile, rather than an actual implementation of checked exceptions. Using this, one can similarly imitate Java throws clauses. // If throw() is empty, it expands to noexcept(true) (same as noexcept), // otherwise it expands to noexcept(false). // A throw statement without parentheses will not expand • define throw(...) noexcept(__VA_OPT__(!)true) import std; using std::runtime_error; class XException : public runtime_error {}; class YException : public runtime_error {}; // throw(Es...) will expand into noexcept(false) void performSomeOperation(int x, int y) throw(XException, YException) { if (x > y) { // throw without a bracket will not expand as a macro throw XException("x > y"); } else if (y > x) { throw YException("y > x"); } std::println("x = y = {}", x); } // throw() will expand into noexcept(true) void willNotThrow(int x) throw() { std::println("x = {}", x); } One can also specify that a function is noexcept conditionally on another function being noexcept, like so: void mightThrow(); // The first noexcept is the noexcept clause, the second is the noexcept operator which evaluates to a Boolean value void f() noexcept(noexcept(mightThrow())); Though C++ has no checked exceptions, one can propagate the thrown object up the stack when inside a catch block, by writing (without specifying an object). This re-throws the caught object. This allows operations to be done within the catch block that catches it, before choosing to allow the object to continue propagating upwards. An uncaught exceptions analyzer exists for the
OCaml programming language. The tool reports the set of raised exceptions as an extended type signature. But, unlike checked exceptions, the tool does not require any syntactic annotations and is external (i.e. it is possible to compile and run a program without having checked the exceptions). In C++, one can also perform "Pokémon exception handling". Like catch (Throwable t) in Java, C++ supports a catch (...) block, which will catch any thrown object. However, catch (...) has the disadvantage of not naming the caught object, which means it cannot be referred to. This is because in languages like Java, only classes which extend java.lang.Throwable may be thrown, while in C++ any type (even primitives) may be thrown, and thus there is no guaranteeably safe way to store a reference to the caught object of a catch-all block. import std; using std::exception; // Catching only exceptions: try { // ... } catch (const exception& e) { // Catching only exceptions: std::println("An exception was caught: {}", e.what()); } catch (...) { // Catching all thrown objects: std::println("An unknown error was caught"); } The
Rust language, instead of using exceptions altogether, represents recoverable exceptions as
result types. This is represented as Result (or expected in C++). The advantage of result types over checked exceptions is that while both result types and checked exceptions force users to immediately handle errors, they can also be directly represented as a return type within the language's
type system, unlike checked exceptions where the declared potentially thrown exception is part of the function signature but not directly part of its return type. == Dynamic checking of exceptions ==