BT

Rescuing Checked Exceptions in Asynchronous Java Code

| Posted by Dario Crivelli Follow 0 Followers on Oct 15, 2014. Estimated reading time: 15 minutes |

The static exception-checking feature offered by the Java language via its checked exception syntax is very useful because it allows the programmer to express complex aspects of the program workflow in a handy way.

In fact, through checked exceptions, a function that is supposed to return data of some kind can easily be extended to alternatively notify the caller of all the different cases in which the supplied input is not suitable for the requested computation, so that each case can trigger the proper action. And the syntax-level enforcement of exception handling provided by the language makes these exceptions a legitimate part of the function signature, like an implicit extension of the return type.

The offered abstraction is particularly handy in programs with a layered structure, where the invoking layer must only know what cases may originate from the invocation of the inner layers, with no further knowledge needed. Then, the invoking layer should only decide which of these cases require a follow up in its own scope vs. which should be considered illegal cases for its scope, and hence should be recursively notified to the outer layer.

This abstraction of special cases to be identified and handled upon an underlying top-down workflow is often the most natural way in which program specifications are expressed in informal terms. Hence, the availability of checked exceptions allows for implementations that, in their visual form, stick as much as possible to the originating specifications.

For instance, a top-down specification of an Internet service may identify, among the various layers, one devoted to the processing of a custom protocol in which requests and responses are expressed. The description of the normal behavior of the layer could give rise to the following code:

String processMessage(String req) {
   MyExpression exp = parseRequest(req);
   MyValue val = elaborate(exp);
   return composeResponse(val);
}

In addition, various kinds of error conditions could be spotted, each of which would cause the interaction with the client to proceed in a different way. Let’s suppose that:

  • parseRequest could identify a “Syntax issue”
    • in this case, the communication stream should be interrupted abruptly;
  • elaborate could identify a “Resource issue” upon a request that assumes the availability of a resource that, instead, is not available
    • in this case, we want to lean on the underlying transport protocol (for instance, an HTTP 404 error) to notify the lack of resource
  • elaborate could also identify a “Credential issue” if the user is trying to perform an operation she is not entitled to
    • in this case, a specific response for the client is available in our custom protocol

By leveraging checked exceptions we could express the layer code in the following way:

Snippet 1:

MyExpression parseRequest(String req) throws MySyntaxException { ... }
String composeResponse(MyValue val) { ... }
String composeErrorResponse(MyCredentialException ce) { ... }

MyValue elaborate(MyExpression exp) throws MyCredentialException, MyResourceException { ... }

String processMessage(String req) throws MySyntaxException, MyResourceException {
   MyExpression exp = parseRequest(req);
   try {
       MyValue val = elaborate(exp);
       return composeResponse(val);
   } catch (MyCredentialException ce) {
       return composeErrorResponse(ce);
   }
}

If checked exceptions were not available, then, to keep the same information, we would need to introduce dedicated types for representing the outcome of each of the functions that may detect special cases. These types would allow us to store all possible cases, including the value produced in the normal case.

Moreover, to achieve the same level of type-based enforcement, we would have to extend the outcome types to encapsulate all operations available on them, so that all cases would be taken into account.

Unfortunately, Java seems not to supply ready-made mechanisms for defining aggregate outcome types of this kind, that is, something like:

Outcome<T, Exc1, Exc2, Exc3>

where T is the normal return value and any added Exc1, Exc2, ... are the possible alternative error cases, such that only one of them would carry a value upon return.

The closest tool we have to that is Java 8’s CompletionStage<T>, which encapsulates the possibility that a function may throw an exception and takes care of ensuring that any further operation on a previous outcome is skipped if an exception has been detected. But this interface is meant to enable “monadic” style code that hides the exceptions as an aspect of the computation that is totally separated from the normal workflow. Hence it is designed for dealing with exceptions (mainly unchecked) for which there is no recovery, and not for custom checked exceptions that are an integral part of the workflow. As a consequence, even though CompletionStage<T> allows for selectively handling some types of exceptions while keeping the other ones, the handling cannot be enforced for any specific case.

So, to model our case with CompletionStage<T> and keep the type-based enforcement, our checked exceptions should rather be included in the base type T and the need for dedicated outcome types would remain.

By sticking to a naive approach and introducing custom dedicated outcome types (but still taking advantage of Java 8’s syntax), the code becomes something like this:

Snippet 2:

class ProcessingOutcome {
   private String resp;
   private MySyntaxErrorNotif se;
   private MyResourceErrorNotif re;

   ......
}

class ParsingOutcome {
   private MyExpression exp;
   private MySyntaxErrorNotif se;

   ......

   public ElaborationOutcome applyElaboration(
           Function<MyExpression,  ElaborationOutcome> elabFun) {
       if (se != null) {
           return new ExtendedElaborationOutcome(se);
       } else {
           return elabFun.apply(exp);
       }
   }
}

class ElaborationOutcome {
   private MyValue val;
   private MyCredentialErrorNotif ce;
   private MyResourceErrorNotif re;

   ......

   public ProcessingOutcome applyProtocol(
           Function<MyValue, String> composeFun,
           Function<MyCredentialErrorNotif, String> composeErrorFun) {
       if (re != null) {
           return new ProcessingOutcome(re);
       } else if (ce != null) {
           return new ProcessingOutcome(composeErrorFun.apply(ce));
       } else {
           return new ProcessingOutcome(composeFun.apply(val));
       }
   }
}

class ExtendedElaborationOutcome extends ElaborationOutcome {
   private MySyntaxErrorNotif se;

   ......

   public ProcessingOutcome applyProtocol(
           Function<MyValue, String> composeFun,
           Function<MyCredentialErrorNotif, String> composeErrorFun) {
       if (se != null) {
           return new ProcessingOutcome(se);
       } else {
           return super.applyProtocol(composeFun, composeErrorFun);
       }
   }
}

ParsingOutcome parseRequest(String req) { ... }
String composeResponse(MyValue val) { ... }
String composeErrorResponse(MyCredentialErrorNotif ce) { ... }

ElaborationOutcome elaborate(MyExpression exp) { ... }

ProcessingOutcome processMessage(String req) {
   ParsingOutcome expOutcome = parseRequest(req);
   ElaborationOutcome valOutcome = expOutcome.applyElaboration(exp -> elaborate(exp));
   ProcessingOutcome respOutcome = valOutcome.applyProtocol(
       val -> composeResponse(val), ce -> composeErrorResponse(ce));
   return respOutcome;
}

Actually, by comparing snippet 1 and snippet 2, we could consider the checked exception feature as being just a syntactic sugar, aimed at rewriting the latter code in the former shorter syntax, while keeping all the benefits of type-based enforcement.

However, there is an annoying problem with this feature: it is available only with synchronous code.

If even a single subtask in our workflow should involve an asynchronous API call and a possibly significant delay, then we might not want to keep the processing thread waiting for the asynchronous computation to complete (barely for performance and scalability reasons).

Hence, any code that is supposed to follow the asynchronous API call, in each of the invoking layers, would have to be moved in to callbacks. In that case the simple recursive structure of snippet 1, which enables static exception checking, would no longer be available.

As a consequence, with asynchronous code, encapsulating the various function outcomes into dedicated return types seems to be the only way of enforcing that each error case will be eventually handled.

Fortunately, by taking advantage of Java 8’s JDK, we can account for the introduction of asynchronicity in a workflow in a way that preserves the code structure. For instance, suppose that the elaborate function should require asynchronous processing. Then it could be rewritten to return a CompletableFuture and the code could become:

Snippet 3:

class ProcessingOutcome {
   private String resp;
   private MySyntaxErrorNotif se;
   private MyResourceErrorNotif re;

   ......
}

class ParsingOutcome {
   private MyExpression exp;
   private MySyntaxErrorNotif se;

   ......

   public CompletableFuture<ElaborationOutcome> applyElaboration(
           Function<MyExpression, CompletableFuture<ElaborationOutcome>> elabFun) {
       if (se != null) {
           return CompletableFuture.completedFuture(new ExtendedElaborationOutcome(se));
       } else {
           return elabFun.apply(exp);
       }
   }
}

class ElaborationOutcome {
   private MyValue val;
   private MyCredentialErrorNotif ce;
   private MyResourceErrorNotif re;

   ......

   public ProcessingOutcome applyProtocol(
           Function<MyValue, String> composeFun,
           Function<MyCredentialErrorNotif, String> composeErrorFun) {
       if (re != null) {
           return new ProcessingOutcome(re);
       } else if (ce != null) {
           return new ProcessingOutcome(composeErrorFun.apply(ce));
       } else {
           return new ProcessingOutcome(composeFun.apply(val));
       }
   }
}

class ExtendedElaborationOutcome extends ElaborationOutcome {
   private MySyntaxErrorNotif se;

   ......

   public ProcessingOutcome applyProtocol(
           Function<MyValue, String> composeFun,
           Function<MyCredentialErrorNotif, String> composeErrorFun) {
       if (se != null) {
           return new ProcessingOutcome(se);
       } else {
           return super.applyProtocol(composeFun, composeErrorFun);
       }
   }
}

ParsingOutcome parseRequest(String req) { ... }
String composeResponse(MyValue val) { ... }
String composeErrorResponse(MyCredentialErrorNotif ce) { ... }
CompletableFuture<ElaborationOutcome> elaborate(MyExpression exp) { ... }
CompletableFuture<ProcessingOutcome> processMessage(String req) {
   ParsingOutcome expOutcome = parseRequest(req);
   CompletableFuture<ElaborationOutcome> valFutOutcome = expOutcome.applyElaboration(exp -> elaborate(exp));
   CompletableFuture<ProcessingOutcome> respFutOutcome = valFutOutcome.thenApply(outcome -> outcome.applyProtocol(
           val -> composeResponse(val), ce -> composeErrorResponse(ce)));
   return respFutOutcome;
}

Preserving the code structure in spite of the introduction of an asynchronous call is a highly desirable feature. In fact, whether the underlying execution proceeds on the same thread or switches (one or multiple times) to a different thread may not always be an important aspect. In our initial top-down specification, we didn’t mention thread matters and we can only assume an obvious hidden requirement in terms of efficiency. Proper error handling is certainly a more significant aspect here.

If we could preserve the structure of  snippet 1 in spite of an underlying thread switch, similarly to how we can preserve the structure of snippet 2, that might provide the most prefered code representation.

Put in another way: because the code in snippet 2 is suitable for an alternative, simpler representation based on checked exceptions, why shouldn’t the same hold for the slightly modified code in snippet 3?

We are not trying to face the question in formal terms nor are we claiming that extending the language in order to enable the above would be possible. We would like just to remark how nice it would be if there were such an extension.

To clarify, let’s assume that Java could recognize when a function is asynchronous but still sequential. For instance, by writing a function in the following way (using a fantasy keyword seq)

CompletableFuture<T> seq fun(A1 a1, A2 a2) { ... }

we could have the JVM enforce, in some way, that the returned CompletableFuture completed only once (by discarding further spurious invocations); this would be considered as the “official” termination of the function, regardless of the actual thread involved.

Then, the compiler could allow us to use the fun function as though it were defined with the following simplified signature (using another fantasy keyword async):

T async fun(A1 a1, A2 a2);

With this signature, we could invoke the function as though it were synchronous, but the JVM should have to take care of extracting all code specified after fun and executing it upon the “official” termination (i.e., upon the completion of the CompletableFuture), in the proper thread.

This code transfer would apply recursively to all functions in the invocation stack. In fact, if the simplified signature of fun is leveraged in the definition a new function, the signature of the latter function should be forced to also include the async keyword, to state that, under the hood, it is asynchronous (yet sequential).

By the way, the recursive propagation could be terminated upon an invocation of a function whose signature is

void async fun(A1 a1, A2 a2);

so as to allow the invoking thread (perhaps belonging to an ExecutorService) to do something else.

The above hypothetical feature could be easily extended to support checked exceptions. In practice, upon a function definition of the form:

CompletableFuture<Outcome<T, E1, E2>> seq fun(A1 a1, A2 a2) { ... }

where Outcome is some standard wrapper for a return type and one or multiple exceptions, the compiler could allow us to use the function as though it were defined with the following simplified signature:

T async fun(A1 a1, A2 a2) throws E1, E2;

By leveraging this syntax, an equivalent version of snippet 3 could be written in the simplified form:

Snippet 4:

MyExpression parseRequest(String req) throws MySyntaxException { ... }
String composeResponse(MyValue val) { ... }
String composeErrorResponse(MyCredentialException ce) { ... }

CompletableFuture<Outcome<MyValue, MyCredentialException, MyResourceException>> seq elaborate(MyExpression exp) { ... }
/*
   equivalent to:
   MyValue async elaborate(MyExpression exp) throws MyCredentialException, MyResourceException;
*/

String async processMessage(String req) throws MySyntaxException, MyResourceException {
   MyExpression exp = parseRequest(req);
   try {
       MyValue val = elaborate(exp);
       return composeResponse(val);
   } catch (MyCredentialException ce) {
       return composeErrorResponse(ce);
   }
}

and the transformation of snippet 1, upon the introduction of asynchronicity in elaborate, into snippet 4 would be a natural one.

Is there any way to achieve something similar (submitting to reasonable compromise) with the available syntax?

What we need to accomplish this is some mechanism by which all code that follows an asynchronous call is delimited (let’s say by transforming it into a callback) and executed after the asynchronous call has produced an outcome, in the thread in which this occurs.

As an intuitive approach, a possible attempt, which is feasible as long as the number of asynchronous calls for each layer is small, could involve the following steps:

  1. Start with a synchronous representation of the workflow (as in snippet 1) and identify the functions that are supposed to become asynchronous (in this case: evaluate and, as a consequence, processMessage itself).
  2. If multiple potentially asynchronous invocations were present in the same function, then arrange the code, possibly by introducing intermediate functions, in such a way that each function contains only one potentially asynchronous invocation in the middle and any other one is called as the last operation upon returning.
    (No arrangement is needed in our simple case).
  3. Transform the code in such a way that each function, let’s say “outer”, involving the invocation of a function, let’s say “inner”, that is supposed to become asynchronous is split into an “outerBefore” part and an “outerAfter” part. outerBefore will include all code that precedes inner, then invoke inner as its last operation; on the other hand, outerAfter will invoke outerBefore as its first operation, then all remaining code. Note that, as a consequence, outerBefore and outerAfter would share the same arguments.
    In our case, we might come out with the following code:

    Snippet 5:

    MyExpression parseRequest(String req) throws MySyntaxException { ... }
    String composeResponse(MyValue val) { ... }
    String composeErrorResponse(MyCredentialException ce) { ... }
    
    String processMessage(String req) throws MySyntaxException, MyResourceException {
       return processMessageAfter(req);
    }
    String processMessageAfter(String req) throws MySyntaxException, MyResourceException {
       try {
           MyValue val = processMessageBefore(req);
           return composeResponse(val);
       } catch (MyCredentialException ce) {
           return composeErrorResponse(ce);
       }
    }
    
    MyValue processMessageBefore(String req)
           throws MySyntaxException, MyResourceException, MyCredentialException {
       MyExpression exp = parseRequest(req);
       return elaborate(exp);
    
    }
    
    MyValue elaborate(MyExpression exp) throws MyCredentialException, MyResourceException {
       return elaborateAfter(exp);
    }
    
    MyValue elaborateAfter(MyExpression exp) throws MyCredentialException, MyResourceException { ... }
    
    ......
  4. Introduce dedicated classes to contain each pair made of a “xxxBefore” and a “xxxAfter” part, then use a temporary instance to invoke any such pair.
    Our code might be extended in this way:

     

    Snippet 6:

    String processMessage(String req) throws MySyntaxException, MyResourceException {
       return new ProtocolHandler().processMessageAfter(req);
    }
    
    class ProtocolHandler {
    
       MyExpression parseRequest(String req) throws MySyntaxException { ... }
       String composeResponse(MyValue val) { ... }
       String composeErrorResponse(MyCredentialException ce) { ... }
    
       String processMessageAfter(String req) throws MySyntaxException, MyResourceException {
           try {
               MyValue val = processMessageBefore(req);
               return composeResponse(val);
           } catch (MyCredentialException ce) {
               return composeErrorResponse(ce);
           }
       }
    
       MyValue processMessageBefore(String req)
               throws MySyntaxException, MyResourceException, MyCredentialException {
           MyExpression exp = parseRequest(req);
           return elaborate(exp);
       }
    }
    
    MyValue elaborate(MyExpression exp) throws MyCredentialException, MyResourceException {
       return new ExpressionHandler().elaborateAfter(exp);
    }
    
    class ExpressionHandler {
       MyValue elaborateAfter(MyExpression exp) throws MyCredentialException, MyResourceException { ... }
    
       ......
    
    }
  5. Replace the instances introduced in the previous step with suitable proxy objects; the job of the proxies will be that of collecting all the “xxxAfter” functions and invoking each of them only after the related “xxxBefore” function has completed (in the thread in which the completion occurs).
    This last step allows for the transformation of the innermost functions into asynchronous ones.
    The final code might become:

     

    Snippet 7:

    String processMessage(String req) throws MySyntaxException, MyResourceException {
       ProtocolHandler proxy = createProxy(new ProtocolHandler());
       return proxy.processMessageAfter(req);
    }
    
    class ProtocolHandler {
    
       MyExpression parseRequest(String req) throws MySyntaxException { ... }
       String composeResponse(MyValue val) { ... }
       String composeErrorResponse(MyCredentialException ce) { ... }
       String processMessageAfter(String req) throws MySyntaxException, MyResourceException {
           try {
               MyValue val = processMessageBefore(req);
               return composeResponse(val);
           } catch (MyCredentialException ce) {
               return composeErrorResponse(ce);
           }
       }
    
       MyValue processMessageBefore(String req)
               throws MySyntaxException, MyResourceException, MyCredentialException {
           MyExpression exp = parseRequest(req);
           return elaborate(exp);
       }
    
    }
    
    MyValue elaborate(MyExpression exp) throws MyCredentialException, MyResourceException {
       ExpressionHandler proxy = createProxy(new ExpressionHandler());
       return proxy.elaborateAfter(exp);
    }
    
    class ExpressionHandler {
    
       MyValue elaborateAfter(MyExpression exp) throws MyCredentialException, MyResourceException { ... }
    
       ......
    
    }

Even after the transformations involved, the final code produced could still be readable as a natural mapping of the initial specifications.

As a side note, we claim that this approach is indeed feasible and, in particular, that the job demanded to the proxies is feasible, because what it essentially does is override the “xxxBefore” and “xxxAfter” methods in the following way:

(let’s consider a proxy for the ProtocolHandler of our example)

  • Proxy.processMessageAfter:
    [this must be the first invocation of this proxy]
    • keep track of the arguments received;
    • find the last proxy invoked and if there is one, notify it; keep track of the finding; then set this as the last proxy invoked;
    • invoke ProtocolHandler.processMessageBefore with the arguments received;
    • if the method has notified it has invoked a further proxy, do nothing more;
    • otherwise the method terminated synchronously; invoke onCompleted (see below) and pass it the method outcome.
  • Proxy.processMessageBefore:
    [this must be invoked from inside ProtocolHandler.processMessageAfter, hence we are in onCompleted (see below) and a method outcome has just been kept]
    • just replay the outcome kept

In addition:

  • Proxy.onCompleted:
    • keep track of the outcome received as argument;
    • set this as the last proxy invoked;
    • invoke ProtocolHandler.processMessageAfter with the arguments kept when the invocation of Proxy.processMessageAfter was received;
    • if the method has notified it has invoked a further proxy, do nothing more; however, take care of informing the further proxy that its previous one should not be this, but our own previous one;
    • otherwise the method terminated synchronously; if there was a previous proxy, invoke its own onCompleted and pass it the method outcome.

The above was just an incomplete sketch.

We tried to apply these ideas to come out with a full solution and what we achieved is an experimental technique that we could apply to a concrete case.

The envisioned technique implies many compromises in terms of usability, which may restrict its appeal to very few scenarios. In our case, it proved to be worth the effort.

The interested reader can find a detailed presentation of our technique here, together with a comprehensive discussion of the usability pros and cons involved.

About the Author

Dario Crivelli is a Computer Scientist at Weswit, the company behind Lightstreamer, an award-winning product for real-time data delivery over the Internet. Dario has been the lead developer of Lightstreamer Server since the beginning.

Rate this Article

Adoption Stage
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Checked Exceptions are THE EVIL by Mario Fusco

Checked exceptions are one of the biggest mistake in history of programming language design and the fact that Java is the only language supporting them clearly demonstrates this.

Also this article is a (unintentional) demonstration of what's wrong in checked exception and why it is crazy to try to use them in an asynchronous computation context.

Re: Checked Exceptions are THE EVIL by Dario Crivelli

Are you refusing checked exceptions only as a programning language feature or also as an abstraction for knowledge representation?
In other words, do you think that snippet 2 above is evil as well?

Re: Checked Exceptions are THE EVIL by Mario Fusco

In snippet 2 you're not using exceptions. I mean that since you're neither throwing nor catching them, you're not using them as proper exceptions, but as value objects representing an error and that's perfectly fine. They couldn't extend the Exception class and you'll obtain exactly the same result.

In fact all problems in asynchronous code arise when you try to use exceptions as you normally do with synchronous calls. The fact that you're trying to use in async call the same patterns and concepts you normally use in sync scenarios generates all the issues you tried to workaround with your contrived solution. You're solving a problem that wouldn't exist at all if you'd accept to give up using exceptions, in the same way as the only way to structurally solve NPEs is avoid using null references at all.

Even more important your code isn't asynchronous or at least what you called the outer method of your execution isn't. How could be asynchronous a method having a signature like:

public Result doSomething() throws CheckedException;

The fact is that for the client of your API the only interesting method is that one, because it is the only one with which the client itself will interact. I don't mind if the internal implementation of that method forks threads, send messages to actors, uses an STM or makes c-beams glitter in the dark near the Tannhäuser Gates. The only thing I care about is the synchronous signature of the "outer method" exposed by your application/library.

Transforming a method that could potentially be async in a sync one causes 2 problems, one related with the single method itself and the other with the usage of your API as a whole.

The first problem is easier to be explained: if that method could/should be async, why does the client of your API should waste cpu cycles waiting for the completion of that method while it could do something more useful?

To understand the second problem is necessary to define how a good API should be designed. Personally I believe that a good API should be made by a minimal set of primitives hiding the incidental complexity but not the complexity related with the domain at hand. Citing Einstein: "Everything should be made as simple as possible but not simpler".

Since your API is made by that minimal set is highly probable that the client of such API will have the need of doing something of more complex than invoking a single primitive. Typically the client will need to compose the results of several invocations, but since these primitives have been made sync it will be obliged to invoke them in a sequential way, waiting for the result of each invocation and losing any hope of parallelization.

In other terms the API you designed treats asyncronicity as incidental complexity hiding it to your end-users. This is profoundly wrong. Asyncronicity, and consequently the parallelizability that it enables, are not something that should be hidden at the first occasion, but something that needs to be leveraged in order to keep busy even the other 7 cores of your shining 8-cores Mac.

To cut it short you're giving something to your users, error management through checked exception, for which 8 or 9 out of 10 will hate you, but also subtracting them something, the possibility to use your API asynchronously, and you can bet that all of 10 of them will hate you for that.

Re: Checked Exceptions are THE EVIL by Victor Grazi

Checked exceptions have the advantage that they alert the programmer issues to guard against. Not sure why that is a bad thing?

Re: Checked Exceptions are THE EVIL by Dario Crivelli

I must clarify that this proposal is not aimed at providing a mean to write interfaces for asynchronous tasks;
it only shows how to write a complete workflow out of the proposed bricks.

Hence, there is no claim that a method like

public Result doSomething() throws CheckedException;

can be invoked by an external caller and behave asynchronously.
In fact, methods like

String processMessageAfter(String req) throws MySyntaxException, MyResourceException

in the context of snippet 7, are never invoked directly, but only through the mediation of a proxy, which alters them in the way that is sketched subsequently.


About snippet 2, which doesn't use exceptions but uses value objects which can carry error conditions, my claim is that checked exceptions are just a more compact way to express the same code.

Actually, if there were an IDE tool which could handle either form, that is:

1) let me write and see the code in a checked-exception-based form and perform the syntax checks and all kind of coding assistance,

2) automatically keep an underlying equivalent value-object-based form, using this form to interface the compiler,

that would be fine for me and I wouldn't ask the Java compiler to natively manage the checked exceptions anymore.

Fusco is right. by phloid domino

Mr Fusco is right. Checked exceptions evil. Perhaps well intentioned, but we all know where that road famously leads.

Beyond the well articulated problems already mentioned, there is a deeper problem of intellectual laziness. THe author's defense of being able to 'return' multiple types of information is actually a compelling argument against checked exceptions.

In fact, it's not just checked exceptions, it's widespread mis-use of the exception mechanism, which does have legitimate uses, but very very few.

It is always wrong to use exception syntax for domain logic. The great fallacy that there is some distinction between 'normal' logic and 'error' logic is the cause of endless design madness.

All possible logic paths have the same importance. Imposing some silly subjective concept of the 'normal' outcome is a mental glitch, which has been indulged by the exception fad.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

6 Discuss

Login to InfoQ to interact with what matters most to you.


Recover your password...

Follow

Follow your favorite topics and editors

Quick overview of most important highlights in the industry and on the site.

Like

More signal, less noise

Build your own feed by choosing topics you want to read about and editors you want to hear from.

Notifications

Stay up-to-date

Set up your notifications and don't miss out on content that matters to you

BT