JAX-RS Providers on Client and on Server

In this article I’d like to explain two things. First, what to do when you want to restrict JAX-RS providers to be used on, for example, client-side only. And second, what are the issues (and how to solve them) with injecting providers when you create and register instances of them directly.

Constraining JAX-RS providers to particular runtime

Some of the JAX-RS providers can be used on the server-side as well as on the client-side. The reusability of providers was among the goals when JAX-RS 2.0 Client API was proposed and designed. Unquestionably this being a useful feature of JAX-RS 2.0 there are cases in which you want to constrain some of the available providers to either client or server. Constraining providers, or Features for that matter, is even more useful when writing a general purpose library that aims to support both sides but each in a slightly different manner.

Creators of JAX-RS 2.0 thought about needs like this and came up with two possible solutions:

Declarative – @ConstrainedTo

@ConstrainedTo annotation can be placed on any JAX-RS provider type (be it interceptor, filter, message body provider or feature) and in Jersey you can place it on your custom providers annotated with @Contract and registered within your application too. Annotated provider is restricted to be picked-up and used only in a specified run-time context, defined by RuntimeType enum value in the annotation.

For example, the Server-Sent Events in Jersey are supported on both, client and server. All you need to do is registering SseFeature and you’re good to go.

Usually, SSE in Jersey works like this: You create an OutboundEvent in your server application and broadcast the message to all connected clients. On the client you receive an InboundEvent and read the data. As you may guess, the SSE extension module contains message body writer used on the server and message body reader for the client. So, to make sure the reader is not accidentally registered on server we annotate the provider as follows and that’s it:

Programmatic – RuntimeType

Registering JAX-RS providers for a specified run-time programmatically is mainly used in Features. Basically you obtain the current RuntimeType from Configuration and act upon it. In case of the mentioned SseFeature the configure method looks like:

The code above does the same thing as placing @ConstrainedTo annotation on every provider listed in the switch block.

However, you’re able to determine the current run-time everywhere where you have access to Configuration. Imagine your provider behaves differently on client than it does on server. Also let’s assume you decide this difference is not that a big of an issue that would force you to create separate providers for both run-times. In this case you can take advantage of RuntimeType (and during runtime), e.g.:

How injection works for JAX-RS provider instances in Jersey

On server, you don’t need to do anything special to properly inject instances and classes of JAX-RS providers and resources (and basically everything managed by HK2 or CDI). Injection works as you would expect. There is no problem to inject anything because of the fact that there is only one server run-time (and only one ServiceLocator) for each application.

On client, you can still inject classes of JAX-RS providers without noticing a difference. However, the situation with provider instances is a little bit different.

Imagine you have a client request filter that you want to initialize with some data and inject it with an instance of MyInjectedService:

Now, you create a JAX-RS client, register your provider with the client and create two web targets:

The second web target requires also JacksonFeature to be able to work with JSON. When you try to invoke a request from either one of these web targets the NullPointerException would be thrown. Why?

Well, service field in MyLoggingFilter would be null because the filter instance doesn’t get injected. The reason behind this is that you (possibly) have multiple client run-times which means you have multiple HK2’s ServiceLocators. Jersey, of course, knows which one should be used but when you think about it we cannot inject the filter. Every web-target (client runtime) has reference to the same instance of MyLoggingFilter (it’s a singleton). In addition, provides ought to be thread-safe so you cannot inject the filter using one service locator and hope that the filter wouldn’t be, at the same time, used from another thread (from a different web-target). It just wouldn’t work ’cause you’d be using wrongly injected service.

To be fair, this would work in the case if Jersey injected a dynamic proxy into service field but we don’t do that. The reason – it’d be pretty slow.

There is another way to solve the injection introduced in Jersey 2.6. Use ServiceLocatorClientProvider to extract ServiceLocator from context in any JAX-RS provider and with locator you can obtain anything previously registered there. The following example shows how to utilize ServiceLocatorClientProvider in the rewritten version of MyLoggingFilter:

  • NicolasNes

    Hi,

    Thank you for the explaination, it helps me undertsand why following “providers” field was not injected by Jersey when registered as singleton.

    public class MyFilter implements ClientRequestFilter {

    @Context
    private Providers providers;

    }

    Even though I can understand the proxy reason, this behaviour of jersey seems to brake the spec. If you take a look at Client.register(Object component) method documentation it says that : “Fields and properties of all registered JAX-RS component instances are injected with their declared dependencies (see {@link Context}) by the JAX-RS runtime prior to use.”
    So injection should be done as if providers as been registered with Client.register(Class component) method.

    Furthermore if you try to make a your code portable using only JAXRS-2.0.1.jar this decision of not doing proxies make your code non portable from Resteasy to Jersey.

    Do you understand my point ? Is there any properties to set so that jersey uses proxy ?
    Thank you