New Features in Jersey Client

Jersey 2.30 comes with multiple new features, and this post describes three new interfaces on the client. They are PreInvocationInterceptor, PostInvocationInterceptor, and InvocationBuilderListener.

Suppose a case that the start of the request is to be logged and even measured. This can be done by ClientRequestFilter, which is usually invoked before the request is wired on the network. However, the filter may be called as a last of the filters in the chain. Sure, it can have the highest priority, but the other filters can have the very same priority! Some long-running operations can be performed before the measuring can actually start. Even worse, the filter may even be skipped from the chain by the previous #abortWith!

PreInvocationInterceptor

For this, PreInvocationInterceptor, the code that executes before the ClientRequestFilters are invoked, has been added to the client request chain. Jersey ensures all the interceptors are invoked with each request. The interceptor contains a single #beforeRequest method, which corresponds to ClientRequestFilter:

    /**
     * The method invoked before the request starts.
     * @param requestContext the request context shared with 
     * ClientRequestFilter.
     */
   void beforeRequest(ClientRequestContext requestContext);

Note that only a single #abortWith is allowed in all PreInvocationInterceptors, otherwise an IllegalStateException is thrown. All the exceptions accumulated in PreInvocationInterceptors are thrown in a single Exception, available through #getSuppressed().

PostInvocationInterceptor

Similarly, ClientResponseFilter seems to be a good place where the total time of the HTTP request can be measured, but similarly to ClientRequestFilter, the response filter may not be invoked at all. For this, PostInvocationInterceptor has been introduced. Jersey runtime ensures that every PostInvocationInterceptor is called. Since an exception can occur during the HTTP request, PostInvocationInterceptor comes with two methods:

 /**
     * The method is invoked after a request when no 
     * is thrown, or the Throwables are resolved 
     * by previous PostInvocationInterceptor.
     *
     * @param requestContext the request context.
     * @param responseContext the response context 
     * of the original Response or response context
     * defined by the new resolving Response.
     */
    void afterRequest(ClientRequestContext requestContext, 
                      ClientResponseContext responseContext);

    /**
     * The method is invoked after a Throwable is caught 
     * during the client request chain processing.
     *
     * @param requestContext the request context.
     * @param exceptionContext the context available to handle the 
     * caught Throwables.
     */
    void onException(ClientRequestContext requestContext, 
                     ExceptionContext exceptionContext);

The #afterRequest method is executed when no exception has been thrown during the HTTP request, #onException method is executed if the exception has been thrown during the request. It is possible to set a response in #onException, and the consecutive PostInvocationInterceptor will execute its #afterRequest method.

The measuring example can look as follows, then:

String response = ClientBuilder.newClient().target("path")
    .register(
    new PreInvocationInterceptor() {
         @Override
          public void beforeRequest(ClientRequestContext requestContext) {
              startTime = System.currentTimeMillis();
          }  
    })
    .register(new PostInvocationInterceptor() {
         @Override
         public void afterRequest(ClientRequestContext requestContext, 
                                  ClientResponseContext responseContext) {
             logDuration(System.currentTimeMillis() - startTime);
         }
        @Override
         public void onException(ClientRequestContext requestContext, 
                                 ExceptionContext exceptionContext) {
             logDuration(System.currentTimeMillis() - startTime);
         }
     })
    .request().get().readEntity(String.class);

InvocationBuilderListener

InvocationBuilderListener is an interface that is inspired by Microprofile REST Client RestClientBuilderListener and it contains a single method:

 /**
     * Whenever an Invocation.Builder is created, (i.e. when
     * WebTarget#request() is called, this method would be invoked.
     *
     * @param context the updated InvocationBuilderContext.
     */
    void onNewBuilder(InvocationBuilderContext context);

InvocationBuilderContext a subset of methods of the Invocation.Builder. It can be used to call the default values of the Invocation.Builder. Since it is invoked at the time Invocation.Builder is instantiated, any consequent calls of the Invocation.Builder‘s methods will replace the defaults set by the InvocationBuilderListener.

For instance, if all the HTTP requests should contain a custom HTTP header, there can be created a feature that would be registered on the client:

  public static class MyFeature implements Feature {
        @Override
        public boolean configure(FeatureContext context) {
            context.register(
                 (InvocationBuilderListener)(l)->
                     l.getHeaders().add("MY_HEADER", "MY_VALUE"));
            return true;
        }
    }

And More!

Jersey 2.30 comes with more new features on the client, most notably a possible hanging when using the Apache Client Connector has been fixed and the Microprofile Rest Client implementation has been allowed to use connectors.

This entry was posted in Jersey. Bookmark the permalink.