BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Rescuing Checked Exceptions in Asynchronous Java Code

Rescuing Checked Exceptions in Asynchronous Java Code

Bookmarks

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
Style

BT