Java has "checked exceptions", which force the caller of the method to either handle an exception or to rethrow it, e.g.

// requires ParseException to be handled or rethrown
int i = NumberFormat.getIntegerInstance().parse("42").intValue();

Other, more recent languages such as Go and Rust use multiple return values instead:

i, err := strconv.Atoi("42")    // Go

match "42".parse::<i32>() {     // Rust
  Ok(n) => do_something_with(n),
  Err(e) => ...,

The underlying concept is similar: The caller of the method has to do something about potential errors and can't just let them "bubble up the stack trace" by default (as would be the case with non-checked exceptions). From some points of view, checked exceptions can be seen as syntactic sugar for alternative return values.

However, checked exceptions are widely disliked. The C# designers made the deliberate decision to not have them. On the other hand, Go and Rust and extremely popular.

Why did this concept (see the bolded sentence above) fail in Java but succeed in Go and Rust? What mistakes did the Java designers make that the Go and Rust designers didn't? And what can we learn about programming language design from that?

  • 16
    Exceptions as an alternative return path and checked exceptions that force all callers to acknowledge that alternative are separate concepts. As it is, your question mixes up the two issues. Commented Jan 11, 2021 at 9:41
  • 2
    @marstato if I take the very average programmer, would he really come back to handle all errors cases properly once he wrote down everything ? Note that I generally perform the catch/log/wrap in IllegalStateException thing in lot of cases by default.
    – Walfrat
    Commented Jan 11, 2021 at 11:16
  • 11
    Are checked exceptions "widely disliked"? Did it "fail" in Java? I didn't read the link about it being disliked in too much detail, but it seems to only mention one or two notable figures and "some developers". And Java seems to still be doing okay, and its popularity would probably come down to many more factors than just this single one. For just about any language feature or syntax (including Go/Rust's way of doing it) you probably can find a few notable figures and some developers who would object to it. I wouldn't read too much into that. Commented Jan 11, 2021 at 17:51
  • 5
    @BernhardBarker I think a lot of the 'dislike' of checked exceptions in Java came from their overuse in early versions. This was compounded by the fact that the ability to wrap checked exceptions in unchecked exceptions wasn't added to the JDK until later. This meant there were a lot of scenarios where errors would get squashed because the developer didn't want to add throws IOException or throws SQLException to every method declaration in the application.
    – JimmyJames
    Commented Jan 11, 2021 at 18:37
  • 3
    @JimmyJames That's the opposite of my argument. Language support for checked exceptions massively reduces cognitive load. Using them correctly requires a little bit of thinking, but significantly less thinking than writing the same program in a language that doesn't have them, so it's disingenuous to regard the thinking required to use them as an increase. I think the problem is that most developers aren't even aware of the need to think about their handling of unchecked exceptions, or the problems that their failure to do so causes. Commented Jan 13, 2021 at 16:55

9 Answers 9


From a scientific point of view, checked exceptions can be seen as alternative return values, e.g.

Exactly. They can be seen that way, and they should be but they aren't.

Using an Error type like is common in Rust, Elm, Haskell, and in some sub-communities in Scala or a special error value as in Go is just an alternative return value indicated in the type system. A checked exception is like an alternative return value, but it doesn't use the normal way of returning values, it is a completely separate, very different way of "returning values". It also sits outside of the type system, and bolts on a completely separate "checked exception" system onto the type system.

But most importantly, it is not just an alternative return value, it is also an alternative control flow.

Another problem with the specific way checked exceptions are implemented in Java, is that they are anti-modular. That is, however, not a fundamental problem of checked exceptions, unlike the ones I mentioned above. There is an idea of Modular Anchored Exceptions, for example, where you can specify something like

int foo() throws like bar { return bar(); }

And you don't have to know (and leak!) which precise exceptions bar can throw. You can even do something like throws like bar except ArrayOutOfBoundsException when you are handling some errors yourself.

  • 1
    I am not exactly sure what this bring, you can just wrap exception in Java. I mean if bar API's change and add one type of checked exception, you don't need to modify the intermediate calls OK, but you still need to deal with the new exception at some point (unless being wrapped already). Also when Java was made, we weren't yet to the point of having module included in languages features. At least not i the mainstreams ones.
    – Walfrat
    Commented Jan 11, 2021 at 11:31
  • 23
    @PanagiotisKanavos: Whether a DNS error is a "real problem" depends upon what one is seeking to do. If one is trying to determine whether network connectivity has become available after a disruption, a DNS failure may be an expected result.
    – supercat
    Commented Jan 11, 2021 at 18:13
  • 8
    A fundamental problem with the design of checked exceptions in Java is that there is no convenient mechanism for distinguishing between exceptions which occurred in situations where the caller was prepared for the consequent disruption in program flow, versus those which occur at times a caller was not thus prepared. Instead, if a function is defined as throwing WoozleException, Java will by default treat all function calls it makes as though they may safely propagate a WoozleException without recording anything about the resulting disruption in program flow.
    – supercat
    Commented Jan 11, 2021 at 18:19
  • 8
    @PanagiotisKanavos: that really depends on the language's culture. For example, languages like Python uses Exceptions liberally and don't necessarily see them as indicating problems. It's just an alternative control structure when you just need to go up and unwind the call stack. In fact everytime you make a loop, Python internally will throw StopIteration exception. Exceptions are also generally safer than status code to avoid "time of check to time of use" race condition.
    – Lie Ryan
    Commented Jan 12, 2021 at 10:19
  • 3
    @LieRyan it depends on the domain more than the language. Some things require fuses, some require indicator lights only. Saying one or the other is bad doesn't help - except for the unchecked status codes returned by C/C++ code. Not that lazy programmers can't short-circuit every check. Every time I see a discussion of Exceptions vs return values I remember the Fuse Replacement guide Commented Jan 12, 2021 at 10:35

Disclaimer: This will, to a degree, be my own personal take on the problem. I love programming language design, I've thought, read, and discussed a lot of about PL design in general, and the hard topic of errors in particular. This gives me some experience, and also an appreciation that beyond objective factors, there is a lot of subjectivity at play. I will try to separate objective from subjective, but I am biased and therefore may err.

TL;DR: Checked Exceptions are poorly integrated in Java.

However, checked exceptions are widely disliked. The C# designers made the deliberate decision to not have them.

Be careful about generalizations.

The current incarnation of Checked Exceptions in Java is widely disliked. The reason for the dislike, however, may be either Checked Exceptions themselves, OR the current incarnation in Java. And possibly a mix of both.

Poor integration

As a simple example, look at the interface of Stream: if you want to use filter, or map, your predicates cannot throw a Checked Exception.

The underlying issue here is that the Java language offers no way to easily handle, and manipulate, lists of Checked Exceptions in a generic context. At the end of the day, I'd like to be able to write:

class Foo<T>
    <R, ME..., FE...> R transform(
        Function<? extends T, ? extends R, throws ME...> mapper,
        Predicate<? extends R, throws FE...> filter
        throws (ME..., FE..., -BarException)
             return filter(mapper(this.element));
        catch (BarException e)
             return default();

And I can't.

Due to the rise of Generics, and of Functional Programming idioms such as Stream, Checked Exceptions have become increasingly more inconvenient as time passes.


We could stop at poor integration and call it a day, concluding that Checked Exceptions in and out of themselves are perfectly fine, and Java just botched it.

A closer look, however, reveals that Exceptions themselves are part of the issue in the first place. Specifically, the main problem of exceptions is that they are not return types.

One of the advantage that Rust or Haskell have in using Return Types to signal Errors is that any advance that allows better compile-time manipulation of the Return Types (meta-programming) simultaneously grants better compile-time manipulation of Error Specifications in function signatures.

On the other hand, using Exceptions means that efforts must be, to a degree, duplicated between meta-programming on Return Types and meta-programming on Exception Specifications.

This comes to a head in Java because Java doesn't support Variadic Generics, but supports Variadic Exception Specifications. It makes it even more complicated to provide proper meta-programming facilities for the Exception Specifications... and is likely the root cause of Java designers having seemingly thrown their hands up.

A language could choose to provide proper meta-programming facilities to manipulate Exception Specifications in signature, and in that case Checked Exceptions would feel first class. Yet, even then, I expect that it would still not be as convenient as manipulating Return Types, because you would then need to manipulate both Return Types and Exception Specifications, leading to extra work -- as demonstrated in computation of noexcept clauses in C++.


The rise of Generics -- notably for Functional Programming -- has led to a rise of Meta Programming which requires manipulating function signatures at compile-time.

Exception Specifications are not as convenient to manipulate in function signatures -- at best they double the work, as Return Types must always be manipulated regardless -- and may not support manipulation at all as it requires extra-work on the part of language designers and language implementers.

Checked Exceptions in Java require Exception Specifications in a language which does not allow manipulating them via meta-programming, making their use awkward to impossible in Generic code, which is increasingly prevalent.

By comparison, Rust's Result type benefits from all the meta-programming machinery available in Rust in general -- without extra expense from designers, implementers, or users -- and therefore offers a much smoother experience.

  • Also, the number of cases that demanded handling exceptions that could not occur. No, StreamReader.close(); isn't going to throw IOException. And the case of getting the UTF-8 character encoder. And too many more.
    – Joshua
    Commented Jan 13, 2021 at 22:09

Checked exceptions can't be treated as just another part of the expression. They must be handled out of line.

With an error return value, you can write a function that converts error values to a default value, for example. Something like this (even if the variant type has no getOrDefault method):

setNumber(getOrDefault(parseInt(stringValue), 0));

With checked exceptions, you have to write this:

int parsedNumber;
try {
    parsedNumber = parseInt(stringValue);
} catch(NumberFormatException exc) {
    parsedNumber = 0;

which is much less wieldy and you can't abstract it. Sure, you could write a parseIntOrDefault function, but you'd also have to write a separate parseLongOrDefault, parseFloatOrDefault, etc. There's no way to write a general "convert parse error to default value" function.

And no, you can't put setNumber inside the try block, because what if setNumber can also throw a NumberFormatException?

  • 3
    "...and you can't abstract it": I haven't written Java for a while, but in C# I could abstract that using a method that takes a lambda (e.g. private T getOrDefault<T>(Func<T> operationThatCanThrow, T defaultValue). Are you sure that isn't possible in Java?
    – Heinzi
    Commented Jan 11, 2021 at 18:04
  • 5
    @Heinzi You also have to write a Func<T> interface that can throw a NumberFormatException, and you have to accept that every call will allocate an object on the heap, which is an immediate no-go if you're worried about performance... Commented Jan 11, 2021 at 18:07
  • 2
    Also, prior to Java 8 (and yes, such versions still matter sometimes) the syntax for doing it was about as unwieldy as just writing the try block. Commented Jan 11, 2021 at 18:08
  • 2
    Makes sense. I only asked out of curiosity.
    – Heinzi
    Commented Jan 11, 2021 at 18:08
  • 1
    @ShadowRanger To your first point: the "some level of generics" is exactly the problem here, because throws clauses need a whole extra set of meta-programming features to support such cases, and can't get them "for free" by being part of the main type system; a different answer makes that point more clearly. To your second point: the question didn't ask about unchecked exceptions (or whatever the equivalent would be with union types - automatically unwrapped monads of some sort, perhaps) so why should answers have to cover them?
    – IMSoP
    Commented Jan 12, 2021 at 21:18

There are three parts to error code:

  1. Code that generates the errors.
  2. Code that handles the errors.
  3. Code that just propagates the errors from part 1 to part 2.

Most programmers don't give part 3 much thought, because most "enterprise" languages use unchecked exceptions where the part 3 code is essentially invisible. In error propagating code, you want to be aware that there are errors potentially propagating through your code, because someone in the call stack above you is going to have to handle those errors.

However, the propagating code doesn't care what those errors are. When new kinds of errors get thrown, or some kinds of errors start being handled further down in the stack, you don't care. You just pass along whatever you get.

The problem with checked exceptions is they force you to care. If I want to start throwing a checked exception, I have to change the type signature of every single propagating function, breaking compatibility. Some of those functions might be in code I have no control over.

In SOLID terms, you're breaking the open-closed principle in a big way, because you can't add a new function at the bottom of the stack without modifying all the propagating functions in the middle.

Go and Rust force you to know the potential for an error exists, but if you are just propagating the error, you don't have to precisely know every potential value it might take.

  • 9
    Sure you do! If they change int Integer.parseInt(String) throws NumberFormatException to int Integer.parseInt(String) throws NumberFormatException, IDontFeelLikeItException then you have to update the callers. But if they change Variant<int, NumberFormatError> to Variant<int, NumberFormatError, IDontFeelLikeItError> you have the exact same problem! Commented Jan 11, 2021 at 17:55
  • 10
    The whole idea of both checked exceptions and using sum types to indicate errors is that changing the possible errors is an interface change. And throwing a new type of error IS an interface change; you can't call the code in the same context that isn't prepared to handle the new error (it only seems that way if you're using a catch all handler, because it's already prepared to handle everything). You can add a function at the bottom of the call stack if you handle the new error (say by wrapping it into an error type that the calling context already knows how to handle).
    – Ben
    Commented Jan 11, 2021 at 23:54
  • 2
    Yes, but it depends how you make the interface. You can make a type that is extensible at the "throw" side and "catch" side without needing to change the interface at the "propagate" side. Commented Jan 12, 2021 at 0:44
  • 2
    I'm not sure it's true that it's easier to propagate a union type / multiple return than an exception: if you call a function that returns [error, result] and want to propagate error rather than handling it, you need to return [error, result] in turn. If you simply assume that all functions return an untyped error as part of their signature, that's the same as checked exceptions with everything marked throws Throwable; you're working around the issue, not handling it.
    – IMSoP
    Commented Jan 12, 2021 at 9:58
  • 4
    The more I re-read this and other people's comments, the more convinced I am that the difference you're observing is to do with the type hierarchy of the errors, not whether they're thrown or returned. It may be true that Java code tends to use more specific error types than Go code, and that may even contribute to how users feel about interacting with them, but that's a completely orthogonal design decision to whether to signal them with union types, multiple return values, or exception throwing.
    – IMSoP
    Commented Jan 12, 2021 at 12:17

There are some good points made in other answers, but I feel each picks out a different aspect, so I'm going to try to bring them together.

Checked vs unchecked errors

The idea of statically checking code is to make "bad" programs impossible to write. In other words, it places constraints on what the programmer can do. Those constraints have a natural tendency to conflict with the programmer's main goal: to efficiently express a desired behaviour.

Language designers can do two things to make this less "painful" for the programmer:

  • Relax the constraints in certain situations, e.g. allowing some categories of errors to go unchecked
  • Provide expressive abstractions which work well alongside the constraints, e.g. short-hand syntaxes for passing an error up the stack

Whether a particular language provides a "good" set of compromises and abstractions is a matter of opinion.

Returning vs throwing

Exceptions are theoretically equivalent to additional return values only in the same sense that a foreach loop is theoretically equivalent to a while loop: they provide a different developer experience.

The primary difference is that exceptions introduce an additional control flow to the program - after a "throw" statement, control automatically jumps to the appropriate "catch" block, rather than the context the subroutine was called from.

This makes some things easier to express, such as a sequence of dependent statements, or elements within an expression, where you want to abort the sequence or expression as soon as any error is encountered, e.g.:

try {
       anotherFunctionThatMightError() + someOtherValue
} catch(error foo) {

However, it makes other things more difficult to express, such as substituting a default value into an expression in case of error:

foo = functionThatMightError() -> ifErrorThen(fallbackValue);

Because exceptions always cause a jump in control, there has to be a separate statement to jump to:

try {
   foo = functionThatMightError();
} catch(error e) {
   foo = fallbackValue;

It would be possible for a language to help the programmer express this, with some kind of inline catch which meant control didn't jump; as far as I know, Java does not. It is also possible for languages without exceptions to help the programmer express the "abort-on-error" case, e.g. Haskell's do notation.

Checked exceptions vs static types

Because throwing and returning are not interchangeable, checked exceptions need to track extra information for each function: rather than { input types, return type } the signature now includes { input types, return type, possible exceptions }. Tools the language provides to do expressive things with return type don't automatically apply to possible exceptions.

For instance, for the function map( someList, someTransform ), the language might provide tools to specify that the return type of map is related to the type of someTransform:

map( List<T> someList, Transform<T,U> someTransform ): List<U>

With checked exceptions, the language now also needs to allow the programmer to express how the possible exceptions thrown by map relate to those thrown from someTransform.

If the language does provide a syntax to express this, the programmer now needs to understand and apply it.

Checked exceptions in Java specifically

Some of the common complaints about checked exceptions in Java are the result of design decisions or common practices in that language.

For instance:

  • The original standard library in Java used checked exceptions heavily, where it might have been more useful to mix it with other types of error handling.
  • Exceptions in Java tend to be of very specific types, leading to long names, and long lists of possible cases in in throws clauses. Some more recent languages make less use of sub-typing for their errors (whether thrown or returned).
  • Java does support leaving some exceptions unchecked, but this is baked into the single-inheritance type hierarchy, rather than on a per-class or even per-instance basis.
  • Java exceptions always capture a stack trace, which is relatively expensive, and not necessary if the error is handled immediately. This is a common design for exceptions, but not actually inevitable.

checked exceptions can be seen as syntactic sugar for alternative return values

This is the modern perspective on this language feature, but the feature was initially intended and used for a different purpose, at which it failed.

Checked exceptions were added to the Java language before its initial release back in 1996. Back then, Java aspired to be a more dynamic and robust C++. Back then, exception support in C++ was rather new and controversial, but the Java designers felt that exceptions were more robust than returning error codes (because it is harder to accidentally ignore an exception that an error code), and made exceptions an integral part of the language. However, since C++ exception support was so new, some aspects of its exception support were still rather experimental at the time, and one such aspect were "exception specifications". Since the Java team could not wait, they opted to finish were that was going, and invented checked exceptions.

And so they had a new tool that they felt revolutionized error handling in software, and used it liberally when writing the first version of the Java API, communicating nearly all error conditions using checked exceptions - and since Java aspired to be a robust language, they were unusually diligent in communicating possible error conditions.

Together, this meant that not only the Java API was fussy about exceptions, but it also forced this onto its callers. For instance, when you do monitor.wait() you have to handle a checked InterruptedException, even when you know that nobody is interrupting threads. Or when you do new InputStreamReader(file, "ascii"), you have to handle an UnsupportedEncodingException, even though ASCII is guaranteed to be supported by the Java spec.

That is, the early Java API overused checked exceptions to communicate error conditions that callers often don't care about.

The pain of this is still felt today because there is no way to remove a checked exception from a throws clause without changing calling code or making the exception type unchecked by changing its class hierarchy, which is often impossible because this single hierarchy is the only way to catch a group of exception types.

That is, the major pain point of checked exceptions is not that checked exceptions are supported, but their overuse by the Java API, which can not be corrected without breaking API compatibility.

But even if that weren't the case, returning union types is strictly superior to checked exceptions for the following reasons:

  • checked exceptions are exceptions. For instance, their construction captures a stack trace, which is quite expensive (you can suppress this if all super classes collaborate to use the 4 argument constructor of Throwable, but doing so might surprise your callers ...)

  • the decision whether the exception is checked depends on the class hierarchy. This means you can't throw the same exception checked from one method, but unchecked from another. It also means that callers can't catch related exceptions of different checkedness as a group.

  • since exceptions are classes they need globally unique names, which tend to be longer than method-specific ones (compare NoSuchElementException to none). Also, classes take more effort to declare.

  • exceptions don't play nice with scoping rules, making their use quite verbose. For instance, we have to write:

    ReallyLongType<With, Many, Generics> result;
    try {
        result = foo();
    } catch (ReallyLongTypeCurrentlyNotAvailableException e) {
        result = defaultValue;

    rather than:

    var result = foo() or defaultValue;

And that's why, in modern java code, we often prefer union types over checked exceptions. For instance, the above can be written as

    var result = foo().orElse(defaultValue); 

if foo returns an Optional.

And what can we learn about programming language design from that?

  • be mindful of the different maturity levels of different language features, and have a process for incubating new language features that allows you to undo mistakes and extensively test the new feature in real world use cases before committing to its perpetual support (I think TC39, which evolves EcmaScript, does this really well).
  • that some error codes can be replaced by exceptions does not imply that all error codes should be.
  • 1
    As with a lot of answers here, this doesn't make clear which differences are between checked exceptions and union types, in general; and which are between Java and other languages, or Java's implementation of checked exceptions and other possible implementations. In particular, the phrase "returning union types is strictly superior to checked exceptions" sounds like a universal truth, but most of the bullet points following it are actually implementation details of Java that another language could easily avoid.
    – IMSoP
    Commented Jan 12, 2021 at 20:59
  • 1
    Also, regarding naming, you say "compare NoSuchElementException to none" but give no evidence that that's a fair comparison. "none" looks suspiciously like the name of a value, not a type, but what type is it a value of? ElementResolverResult perhaps? Java is certainly notorious for its long-winded type names, but I think that's as much cultural as anything else.
    – IMSoP
    Commented Jan 12, 2021 at 21:27
  • The question already says that checked exceptions are conceptually equivalent to union types. OP already knows there is no difference, so why should I restate that fact?
    – meriton
    Commented Jan 12, 2021 at 21:27
  • As for NoSuchElementException, my answer does give a reason: A name that must be globally unique tends to be longer than one that is only locally unique. The larger the scope, the longer the names ...
    – meriton
    Commented Jan 12, 2021 at 21:29
  • To the first comment: I didn't say that comparing Java to other languages was a bad approach to the question; I suggested that you could be clearer that that's what you're doing, rather than making statements that seem to be comparing the concepts, like the one I quoted. To the second comment: what is the context where "none" is a local name, but otherwise equivalent to "NoSuchElementException"?
    – IMSoP
    Commented Jan 12, 2021 at 21:30

Short answer: It is too easy to just "try catch swallow" everything in java*. Where in Rust and Go, it's much easier to just write good error control code 'correctly', and make it hard to do poorly. i.e., "Falling into the pit of success" programming paradigm.

I strongly suggest you read Joe Duffy's blog on error handling strategies, which were looked at while developing the Midori OS

* Perhaps since java is a common University language, this "try catch swallow" 'strategy, is a bad habit picked up as a student; whose biggest problem was just getting the code to compile, then maybe actually putting out the correct output. Maintainability and code quality were not their biggest concerns.

  • Thanks for the blog link, that was the most interesting thing I've read in a while!
    – Heinzi
    Commented Jan 31, 2021 at 16:01
  • @Heinzi I found almost all of Joe Duffy's bog posts fascinating. I am very sad that Midori OS didn't get developed further. It will be interesting to see what MS does with their extended version of rust they are planning on using for Windows. Commented Feb 1, 2021 at 3:31

The problem with using always the return value approach is that often the developers end up mixing business logic and error handling. Beyond return values, error handling could be about:

  1. Resource handling, closing access to files or database connections, even though this point was later addressed with the construct try/with resource
  2. Transaction handling. When multiple services and databases are involved rolling back a transaction might not be straightforward.
  3. Detailed logging. sending messages to external audit systems, bug reports.
  4. Rather than an external audit system there might be a full separate error handling platform notified by messages sent in the catch block.
  5. Eventual retry. The exception might have been caused by a temporary condition like a timeout or a missing network.

Many of those points were specific to enterprise environments, developers of desktop application might simply complain about the restrictions without thinking about it. But Java is designed for a quite different environment.

  • 1
    This feels like it's answering a different question.
    – IMSoP
    Commented Jan 13, 2021 at 16:08
  • @IMSoP I explained a little bit better et the top.
    – FluidCode
    Commented Jan 13, 2021 at 19:32
  • But the question didn't ask about the problems of return values for error handling; it asked why they are perceived as better than exceptions. Is your intent to challenge the premise of the question by presenting the contrary case?
    – IMSoP
    Commented Jan 13, 2021 at 20:31
  • @IMSoP The question contain the statement "However, checked exceptions are widely disliked." to that statement I would add "by those who do not understand exception handling". Answering the question literally would mean add BS to BS.
    – FluidCode
    Commented Jan 13, 2021 at 21:05

Awkward typing

The type winds up being T with a type union of A, B, C.

This is an odd type that doesn't work best with other things, for example


There isn't a good way to do a generic with variadic check exception.


In Java, every exception must extend Throwable with a stack trace.

You could invent solutions to these, and you'd likely wind up with something pretty close to Go/Rust.

  • 3
    I have no idea what the first paragraph of this answer means. The type of what? What are A, B and C? What's "odd" about it? What "other things" does it not work with?
    – IMSoP
    Commented Jan 12, 2021 at 12:06
  • Do you mean the typing of thrown exceptions (when a method throws multiple ones) is awkward? If so, I'm not quite following your argument. Why do you need to do a union at all? Methods in Java can be declared to throw exceptions of different types, or they can just be declared to throw the generic Exception class if you really want (which isn't good practice in Java, but more closely match what happens in Go, so that doesn't seem to be an reason why exception handing in Go "works better"). Commented Jan 12, 2021 at 17:19
  • "every exception must extend Throwable with a stack trace" - Yes, exceptions must directly or indirectly extend Throwable, but that's just stating how things work in Java. Why do you consider this to be a problem? Commented Jan 12, 2021 at 17:22
  • 2
    The construction of a Throwable usually involves capturing a stack trace, which is an expensive operation which is likely unnecessary if the caller can gracefully recover from the error.
    – meriton
    Commented Jan 12, 2021 at 21:18

Not the answer you're looking for? Browse other questions tagged or ask your own question.