It is quite possible many developers have run into this problem with Jersey, not really a problem, but limitations of a programming language. I remember from school days where C++ Templates had quite a few reference books and it always kept me away from using STL containers 🙂
In Java, we have Generics since 1.5 which looks lot like C++ Templates, but they are not the same. I am not going to cover the details here, just google it. But, Generics have grown in such complexity that it has dedicated 500+ pages FAQ written and maintained by Angelika Langer for years (JLS 3rd edition is only 684 pages).
Long story short: Generics provides compile time type safety and thus eliminating the need for casts. It is achieved through a compile time phenomenon called type erasure. The Generics FAQ explains everything in detail and it is the Java Generics Bible at least for me.
There are cases when we need to return parameterized types from a JAXRS resource method in the Response. Due to type erasure, it requires special handling in Jersey runtime to determine the generic type that is required to select a suitable MessageBodyWriter.
There are couple options available for JAX-RS developers using Jersey and I am going discuss each one of them in detail.
Let us consider a simple domain model, Employee.
import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name = "employee") public class EmployeeBean { private Long id; private String firstName; private String lastName; public EmployeeBean() { // required for JAXB } public EmployeeBean(Long id, String firstName, String lastName) { this.id = id; this.firstName = firstName; this.lastName = lastName; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } }
The employee resource shows an example implementation.
@Path("/employees") public class EmployeeResource { @GET public Collection<EmployeeBean> getEmployees() { EmployeeBean emp = new EmployeeBean(1L, "John", "Doe"); return Collections.singletonList(emp); } }
In this case, we return the Collection of EmployeeBean from the resource method. The following XML is produced on accessing this resource at http://localhost:9998/employees.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <employeeBeans> <employee> <firstName>John</firstName> <id>1</id> <lastName>Doe</lastName> </employee> </employeeBeans>
I would expect to see the list of employees inside <employees> tag instead of <employeeBeans> tag. Hmm, it requires some tweaks to produce that format. So, lets write Employees POJO which embeds the Collection.
@XmlRootElement public class Employees { private List<EmployeeBean> employee; public Employees(List<EmployeeBean> employee) { this.employee = employee; } public Employees() { // required for JAXB } public List<EmployeeBean> getEmployee() { return employee; } public void setEmployee(List<EmployeeBean> employee) { this.employee = employee; } }
Let us add couple methods to the EmployeeResource using Customers POJO that produces a more relevant XML.
@GET @Path("test1") public Employees getEmployees1() { EmployeeBean emp = new EmployeeBean(1L, "John", "Doe"); Employees employees = new Employees(Collections.singletonList(emp)); return employees; } @GET @Path("test2") public Response getEmployees2() { EmployeeBean emp = new EmployeeBean(1L, "John", "Doe"); Employees employees = new Employees(Collections.singletonList(emp)); return Response.ok(employees).build(); }
Now, accessing http://localhost:9998/employees/test1 or http://localhost:9998/employees/test2 should produce the following XML.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <employees> <employee> <firstName>John</firstName> <id>1</id> <lastName>Doe</lastName> </employee> </employees>
But, do we need this silly logic to produce this output? Not anymore, this has been improved in Jersey 1.2 release. Enabling FEATURE_XMLROOTELEMENT_PROCESSING feature in resource configuration should produce this format out of the box. So, accessing http://localhost:9998/employees/test1 should produce this format of XML. This property is disabled by default.
Now, lets dive into our actual problem of type erasure in case of a parameterized type returned in the JAX-RS Response. I have added another method to our EmployeeResource.
@GET @Path("test3") @Produces(MediaType.APPLICATION_XML) public Response getEmployees3() { EmployeeBean emp = new EmployeeBean(1L, "John", "Doe"); List<EmployeeBean> list = new ArrayList<EmployeeBean>(); list.add(emp); return Response.ok(list).build(); }
Now, accessing this method at http://localhost:9998/employees/test3 should spit the following exception. I believe this exception trace is familiar to most Jersey/JAX-RS users.
SEVERE: A message body writer for Java class java.util.ArrayList, and Java type class java.util.ArrayList, and MIME media type application/xml was not found Jul 24, 2010 11:58:55 PM com.sun.jersey.spi.container.ContainerResponse write SEVERE: The registered message body writers compatible with the MIME media type are: application/xml -> com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$App com.sun.jersey.core.impl.provider.entity.DocumentProvider com.sun.jersey.core.impl.provider.entity.SourceProvider$SourceWriter com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$App com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$App */* -> com.sun.jersey.core.impl.provider.entity.FormProvider com.sun.jersey.server.impl.template.ViewableMessageBodyWriter com.sun.jersey.core.impl.provider.entity.StringProvider com.sun.jersey.core.impl.provider.entity.ByteArrayProvider com.sun.jersey.core.impl.provider.entity.FileProvider com.sun.jersey.core.impl.provider.entity.InputStreamProvider com.sun.jersey.core.impl.provider.entity.DataSourceProvider com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$General com.sun.jersey.core.impl.provider.entity.ReaderProvider com.sun.jersey.core.impl.provider.entity.DocumentProvider com.sun.jersey.core.impl.provider.entity.StreamingOutputProvider com.sun.jersey.core.impl.provider.entity.SourceProvider$SourceWriter com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$General com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$General Jul 24, 2010 11:58:55 PM com.sun.jersey.spi.container.ContainerResponse traceException SEVERE: Mapped exception to response: 500 (Internal Server Error) javax.ws.rs.WebApplicationException
To fix this, we need to somehow tell the JAX-RS runtime the type of the response entity, in this case a Collection of Employees. JAX-RS API GenericEntity comes to the rescue. GenericEntity can be used to represent a response entity of a generic type. The EmployeeResource method is updated to use the GenericEntity class when returning a Collection type.
@GET @Path("test4") @Produces(MediaType.APPLICATION_XML) public Response getEmployees4() { EmployeeBean emp = new EmployeeBean(1L, "John", "Doe"); List<EmployeeBean> list = new ArrayList<EmployeeBean>(); list.add(emp); GenericEntity entity = new GenericEntity<List<EmployeeBean>>(list) {}; return Response.ok(entity).build(); }
Accessing http://localhost:9998/employees/test4 should produce the desired output.
In addition to this approach, Jersey 1.2 introduced a new API JResponse to support this better. JResponse is a type safe alternative to Response that preserves the type information of response entity thus it is not necessary to utilize GenericEntity.
The updated Employee resource using JResponse is shown below.
@GET @Path("test5") @Produces(MediaType.APPLICATION_XML) public JResponse<List<EmployeeBean>> getEmployees5() { EmployeeBean emp = new EmployeeBean(1L, "John", "Doe"); List<EmployeeBean> list = new ArrayList<EmployeeBean>(); list.add(emp); return JResponse.ok(list).build(); }
Accessing http://localhost:9998/employees/test5 should produce the desired output.
Both these approaches are easy to implement. The major difference is GenericEntity is a JAX-RS API while JResponse is a Jersey API, which may not work with other JAX-RS implementations and thus not portable. If you are just using Jersey, then JResponse is the preferred way as it is type safe and provides all capabilities of the Response.
Here is the Server code that uses Grizzly (provided for completion) :
import com.sun.grizzly.http.SelectorThread; import com.sun.jersey.api.container.grizzly.GrizzlyWebContainerFactory; import com.sun.jersey.core.util.FeaturesAndProperties; import javax.ws.rs.core.UriBuilder; import java.io.IOException; import java.net.URI; import java.util.HashMap; import java.util.Map; public class Main { private static int getPort(int defaultPort) { String port = System.getenv("JERSEY_HTTP_PORT"); if (null != port) { try { return Integer.parseInt(port); } catch (NumberFormatException e) { } } return defaultPort; } private static URI getBaseURI() { return UriBuilder.fromUri("http://localhost/").port(getPort(9998)).build(); } public static final URI BASE_URI = getBaseURI(); protected static SelectorThread startServer() throws IOException { final Map<String, String> initParams = new HashMap<String, String>(); initParams.put("com.sun.jersey.config.property.packages", "com.employee.resources"); initParams.put(FeaturesAndProperties.FEATURE_XMLROOTELEMENT_PROCESSING, "true"); System.out.println("Starting grizzly..."); SelectorThread threadSelector = GrizzlyWebContainerFactory.create(BASE_URI, initParams); return threadSelector; } public static void main(String[] args) throws IOException { SelectorThread threadSelector = startServer(); System.out.println(String.format("Jersey app started with WADL available at " + "%sapplication.wadlnTry out %shelloworldnHit enter to stop it...", BASE_URI, BASE_URI)); System.in.read(); threadSelector.stopEndpoint(); } }
I hope this clarifies some of the underlying behavior of handling parameterized types in JAX-RS applications.
Thanks for the post. It provided *exactly* what I needed. I was in a time crunch and it was such a help to find material that addressed my immediate need with nothing more than a little copy/paste/modify.
LikeLike
Hi,
I still have the same problem after the use of GenericEntity with the MediaType application/json. With xml, no problem. Do you have any idea why ?
Thanks.
LikeLike
Did you include the Jersey-Json dependency, which is required for JSON rendering? Adding this to your POM should fix this problem.
[xml]
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.3</version>
</dependency>
[/xml]
-Arul
LikeLike