How to properly inject CDI beans into JAX-RS sub-resources

Jakarta REST (JAX-RS) defines it’s own dependency injection using the @Context annotation. REST resources also support CDI injection if you enable CDI on the REST resource class (e.g. using a bean-defining annotation like @RequestScoped).

But injection doesn’t work out of the box on JAX-RS sub-resources. How to create sub-resources so that both injection mechanisms work also in sub-resources? I’ll show you, it’s very easy.

How to do it (for the impatient)

  • Inject the sub-resource into the JAX-RS resource via the @Inject annotation
  • Initialize the sub-resource using the ResourceContext object injected via the @Context annotation
@Path("request")
@RequestScoped
public class RestResource {

    @Inject // inject subresource as CDI bean
    SubResource subResource;
    
    @Context // inject from JAX-RS container
    ResourceContext resourceContext;
    
    @Path("subresource")
    public SubResource getSubresource() {
        return resourceContext.initResource(subResource);
    }
}Code language: CSS (css)

Full story

First, let’s briefly explain what a sub-resource is. It’s a class that looks similar to a usual JAX-RS resource but it’s not used standalone. Instead, an instance of this class can be returned from a JAX-RS resource to map it to a subpath of that resource. Therefore a JAX-RS resource can delegate processing of a specific subpath to another class. Sub resources look like any other JAX-RS resources but don’t specify the @Path annotation. The path is defined on the resource method that returns the sub resource:

// Sub-resource - no @Path annotation on the class
@RequestScoped
@Produces(MediaType.TEXT_PLAIN)
public class SubResource {
    
    @GET
    public String getMessage() {
        return "This is a sub-resource.";
    }
}Code language: PHP (php)
@Path("request") // defines the path "request" for this resource
@RequestScoped
@Produces(MediaType.TEXT_PLAIN)
public class RestResource {

    @GET
    public String getMessage() {
        return "This is a JAX-RS resource.";
    }

    @Path("subresource") // defines the subpath for the sub-resource: "request/subresource"
    public SubResource getSubresource() {
        return new SubResource();
    }
}Code language: PHP (php)

If you access path /request, the response will contain “This is a JAX-RS resource.”

If you access path /request/subresource, the response will contain “This is a sub-resource.”

However, the catch is that with the simple example above, no injection works in the subresource. it’s not possible to inject anything into the SubResource class, any atempt to do so will result in null values for injected fields. It’s created as a plain Java object in the getSubresource() method and thus it’s not managed by any container. The @RequestScoped annotation is ignored in this case and anything marked with the @Inject or @Context annotation would be also ignored and remain null.

Injection works on JAX-RS resources because they are created by the JAX-RS container and not using the new keyword. If CDI is also enabled on the resource class, Jakarta EE runtime will first create the JAX-RS resource as a CDI bean and then pass it to the JAX-RS container, which then does its own injection.

None of this happens if a sub resource is created using new, therefore we must create it in a different way.

Solution

2 simple steps are needed to add support for both types of injection in JAX-RS sub-resources:

  • Inject the sub-resource into the JAX-RS resource. This enables the CDI injection
  • Initialize the sub-resource using the ResourceContext object provided by the JAX-RS container. This enables JAX-RS injection for values annotated with @Context

This is how the RestResource class should look like to create the sub-resource properly:

@Path("request")
@RequestScoped
@Produces(MediaType.TEXT_PLAIN)
public class RestResource {

    @Inject // inject subresource as CDI bean
    SubResource subResource;
    
    @Context // inject from JAX-RS container
    ResourceContext resourceContext;
    
    @GET
    public String getMessage() {
        return "This is a JAX-RS resource.";
    }

    @Path("subresource")
    public SubResource getSubresource() {
        return resourceContext.initResource(subResource);
    }
}Code language: PHP (php)

NOTE: You need to use the initResource method of ResourceContext and not the getResource method. The getResource method creates a new JAX-RS sub-resource from a class but it’s not guaranteed that it also enables CDI injection for it. Although some Jakarta EE runtimes will enable CDI injection if you call the getResource method, it’s known that some of them like OpenLiberty and Payara don’t do it. In the future, this will very probably be improved when the @Context injection will be replaced by CDI injection, which is already planned.

Now you can use both types of injection and all will work as expected:

@RequestScoped
@Produces(MediaType.TEXT_PLAIN)
public class SubResource {

    @Inject
    SomeCdiBean bean;

    @Context
    UriInfo uriInfo
    
    @GET
    public String getMessage() {
        return bean.getMessage() + ", path: " + uriInfo.getPath();
    }
}Code language: CSS (css)

Leave a Reply

Your email address will not be published. Required fields are marked *

Captcha loading...