Exception Handling

Hello everyone,
I’ve been writing my web app using Play 2.7, and most of it was an awesome experience, I have a question about Exception handling though.

I have a Repository method that returns a CompletionStage<List> if everything went fine but also might throw an unchecked exception, for example an IllegalArgumentException.

From the Controller I call this Repository method, and chain it with a .exceptionally() to handle those exceptions. I noticed that this .exceptionally() method doesn’t get called, and a red page with the stacktrace is displayed in the web browser as if that exception wasn’t being handled.

Is this normal? Should I override https://www.playframework.com/documentation/2.8.x/JavaErrorHandling in some way? I thought that the point of it was to return custom messages for unhandled exceptions, not that it’d take precedente over handled ones.

Thank you and happy vday!
Roberto

I’ve proceeded to override HtmlOrJsonHttpErrorHandler with both a custom html and a custom json error handlers, but my issue still remains, the .exceptionally() handler is never called because the exception is trapped by these handlers instead…

I am pretty sure it should get called, I use exceptionally() in my code. Do you have any sample snippets you can share?

Dear chrono_b, thank you for your help.
This is the repository method:

    @Override
    public CompletionStage<List<?>> getTabularData(Map<String, String[]> parameters) {
        String flow = parameters.containsKey("flows") ? parameters.get("flows")[0] : parameters.get("flows[]")[0];
        switch (flow) {
            case "A":
....
            case "B":
...
            default:
                throw new IllegalArgumentException("Received an unsupported flow: " + flow);
        }
    }

and this is the controller method that calls it:

return repo.getTabularData(request.queryString()).exceptionally(ex -> {
	ex.printStackTrace();
	return new ArrayList<>();
}).thenApplyAsync(list -> {
	if(!list.isEmpty())
		return ok(toJson(list));
	else
		return notFound(this.messagesApi.preferred(request).at("graph.error.generic.noDataFound"));
}

In case I pass an unsupported flow I was expecting the .exceptionally() block to handle it, but it appears that instead the error handling of play is intercepting it and exceptionally() is never called.

Have a nice day,
Roberto

Looks like it should work. I mocked something similar up where I simply throw an IllegalArgumentException in the getTabularData() call, and the exceptionally block gets called. I did something like this:

public CompletionStage<Result> test() {
        return CompletableFuture
                .supplyAsync(() -> {
                    throw new IllegalArgumentException("some error");
                })
                .exceptionally(ex -> {
                    ex.printStackTrace();
                    return new ArrayList<>();
                })
                .thenApplyAsync(result -> ok());
}

Ummm I quite possibly then lack some basics of how CompletionStage works, my understanding was that this was a proper way to do things:

  1. a DAO method that accesses the database does so in a supplyAsync block and returns a CompletionStage of something (possibly Optional of something)
  2. a Repository method receives a call from a Controller, it first looks if it has a datasource for a certain tenant, and if it does it calls the proper dao method. This method also returns a CompletionStage to the controller, and received a CompletionStage from the dao. My idea was that this method would look like this:
CompletionStage<T> repositoryMethod(String tenant) throws InvalidTenantException {
//checks input, for example something along these lines
if(tenantMap.containsKey(tenant)){
    return tenantMap.get(tenant).callDaoMethod()...
    // this works fine because callDaoMethod also returns a CompletionStage<T>
} else {
// handle invalid request parameter
throw new InvalidTenantException("Tenant"+tenant+" unknown");
}
}
  1. a Controller calls the above method and handles possible exceptions via .exceptionally()

I was under the impression that this would be fine, but the controller’s exceptionally() block isn’t getting called.
If I change it to:

CompletionStage<T> repositoryMethod(String tenant) throws InvalidTenantException {
//checks input, for example something along these lines
if(tenantMap.containsKey(tenant)){
    return tenantMap.get(tenant).callDaoMethod()...
    // this works fine because callDaoMethod also returns a CompletionStage<T>
} else {
// handle invalid request parameter
supplyAsync(() -> {
 throw new InvalidTenantException("Tenant"+tenant+" unknown");
               };
}
}

It does. Is it because exceptionally only intercepts CompletionException? But then why a javax.PersistenceException that is thrown in the DAO and passed to the Repository and all the way to the Controller is properly handled by .exceptionally()?

Yes, it can be confusing. I myself is still learning new things everyday :slight_smile:

Anyway, I am not sure if it is correct to say that exceptionally() only catches CompletionException (although whatever exception you throw will always be wrapped in a CompletionException, so I can see why you would think that), but it is supposed to be part of your “CompletionStage chain”. Your function is always returning a Future/Promise, it either completes successfully with a result or errors out with an exception, so whatever you return needs to be that. If you just throw an exception, it is not a CompletionStage.

You probably should use CompletableFuture’s completeExceptionally(throwable) method.

Your DAO is wrapped in a supplyAsync() block, so if you throw an exception in there, it is still “within” the returned CompletionStage only it has an exception instead of a value (and the exception you throw is automatically wrapped in a CompletionException), so your controller’s exceptionally() method handles it appropriately.

I am probably not explaining this very well, and you might want to read up on the official doc on this.

1 Like

I read it but your explanation was much cleaner :-)
Thank you very much for helping me understand this!

Interesting topic, I learn and read a lot of useful things here. :+1: Thanks!