Enterprise Java

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<strong> </strong>subResource;
    
    @Context // inject from JAX-RS container
    ResourceContext resourceContext;
    
    @Path("subresource")
    public SubResource getSubresource() {
        return resourceContext.initResource(subResource);
    }
}

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.";
    }
}
@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();
    }
}

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<strong> </strong>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);
    }
}

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();
    }
}

Published on Java Code Geeks with permission by Ondrej Mihalyi, partner at our JCG program. See the original article here: How to properly inject CDI beans into JAX-RS sub-resources

Opinions expressed by Java Code Geeks contributors are their own.

Ondrej Mihalyi

Ondrej is a lecturer and consultant inventing and evangelizing new approaches with already proven Java tooling. As a Scrum Master and expert in Java EE ecosystem, he helps companies to build and educate their developer teams, improve their development processes and be flexible and successful in meeting client requirements.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button