quarkus: Exceptions thrown in Rest client should be more specific

Description

Currently when using the Quarkus rest client (regular & reactive), any response other than a 2xx is thrown as a javax.ws.rs.WebApplicationException (or a RESTEasy subclass of it). In Quarkus using RESTEasy Reactive, the exception is a org.jboss.resteasy.reactive.ClientWebApplicationException, which is a subclass of javax.ws.rs.WebApplicationException. JAX-RS exposes more fine-grained exceptions for lots of http statuses.

Because of this, using fault tolerance handling is very difficult, as you aren’t able to use the built-in skipOn fields in the fault tolerance annotations.

Say I want to ignore a circuit breaker/fallback/retry on a specific http status? I can’t use the fault tolerance exception because everything is always a javax.ws.rs.WebApplicationException.

As an example, say I want to ignore a circuit breaker if a client call returns a 404. I have to implement some custom logic:

HeroRestClient.java:

@Path("/api/heroes")
@Produces(MediaType.APPLICATION_JSON)
@RegisterRestClient(configKey = "hero-client")
interface HeroRestClient {
  @GET
  @Path("/random")
  Uni<Hero> findRandomHero();
}

HeroClient.java:

@ApplicationScoped
public class HeroClient {
  private final HeroRestClient heroClient;

  public HeroClient(@RestClient HeroRestClient heroClient) {
    this.heroClient = heroClient;
  }

  @CircuitBreaker(requestVolumeThreshold = 8, failureRatio = 0.5, delay = 2, delayUnit = ChronoUnit.SECONDS)
  @CircuitBreakerName("findRandomHero")
  CompletionStage<Hero> getRandomHero() {
    // Want the 404 handling to be part of the circuit breaker
    // This means that the 404 responses aren't considered errors by the circuit breaker
    return this.heroClient.findRandomHero()
      .onFailure(throwable -> 
        Optional.ofNullable(throwable)
          .filter(t -> t instanceof WebApplicationException)
          .map(WebApplicationException.class::cast)
          .map(WebApplicationException::getResponse)
          .filter(response -> response.getStatus() == Status.NOT_FOUND.getStatusCode())
          .isPresent()
      ).recoverWithNull()
      .subscribeAsCompletionStage();
  }
}

When the JAX-RS spec already has exceptions that map to most of the known http status codes. If that was the case, then my custom code could be completely replaced with something like

@Path("/api/heroes")
@Produces(MediaType.APPLICATION_JSON)
@RegisterRestClient(configKey = "hero-client")
interface HeroRestClient {
  @GET
  @Path("/random")
  @CircuitBreaker(
    requestVolumeThreshold = 8, 
    failureRatio = 0.5, 
    delay = 2, 
    delayUnit = ChronoUnit.SECONDS,
    skipOn = NotFoundException.class
  )
  @CircuitBreakerName("findRandomHero")
  Uni<Hero> findRandomHero();
}

See https://github.com/smallrye/smallrye-fault-tolerance/issues/524#issuecomment-1061891842 for some conversation around this as well.

Implementation ideas

No response

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 6
  • Comments: 22 (20 by maintainers)

Commits related to this issue

Most upvoted comments

Adding my +1

I think its because in org.jboss.resteasy.reactive.client.impl.RestClientRequestContext there is

    @Override
    protected Throwable unwrapException(Throwable t) {
        var res = super.unwrapException(t);
        if (res instanceof WebApplicationException) {
            var webApplicationException = (WebApplicationException) res;
            var message = webApplicationException.getMessage();
            var invokedMethodObject = properties.get(MP_INVOKED_METHOD_PROP);
            if ((invokedMethodObject instanceof Method) && !disableContextualErrorMessages) {
                var invokedMethod = (Method) invokedMethodObject;
                message = "Received: '" + message + "' when invoking: Rest Client method: '"
                        + invokedMethod.getDeclaringClass().getName() + "#"
                        + invokedMethod.getName() + "'";
            }
            return new ClientWebApplicationException(message, webApplicationException,
                    webApplicationException.getResponse());
        }
        return res;
    }

Seems I’d have to invent my own Exception subclass rather than re-use one of the existing JAX-RS ones, since they all extend from WebApplicationException. The resteasy reactive client will always wrap any WebApplicationException in a org.jboss.resteasy.reactive.client.impl.ClientWebApplicationException. This seems inefficient to me, since the exception classes that map to http status codes are already there. Why should I have to invent my own?

UPDATE: I verified this is the case. This works if I create my own exception class rather than re-use one of the default ones, which isn’t something I really want to do.