JAX-RS List Generic Type Erasure
How to Maintain a List’s Generic Type
The problem is that when you want to return a List from a JAX-RS resource method the generic type is lost.
The following code results in type loss
@GET @Produces(MediaType.APPLICATION_JSON) public Response getAllBooks() { List<Book> books = BookRepository.getAllBooks(); // queries database for all books return Response.ok(books).build(); }
and the following exception:
MessageBodyWriter not found for media type=application/json, type=class java.util.Arrays$ArrayList, genericType=class java.util.Arrays$ArrayList
Luckily JAX-RS is packaged with a solution in the form of the GenericEntity class which is designed to maintain the generic type. To use this class just wrap the Collection in the GenericEntity like shown in this code:
import javax.ws.rs.core.GenericEntity; ... @GET @Produces(MediaType.APPLICATION_JSON) public Response getAllBooks() { List<Book> books = BookRepository.getAllBooks(); // queries database for all books GenericEntity<List<Book>> list = new GenericEntity<List<Book>>(books) {}; return Response.ok(list).build(); }
I was banging my head against the wall trying to figure this out but thanks to this post by Adam Bein I was saved. Hopefully, this post finds you and stops any headaches before they begin.
@XmlRootElement public class Book { private String isbn; private String title; private String author; private Float price; public Book() { } public Book(String isbn, String title, String author, Float price){ this.isbn = isbn; this.title = title; this.author = author; this.price = price; } // Getters and Setters removed for brevity }
Important Update
The behaviour described above is demonstrated on GlassFish 4.1 and produces the following exception in the server.log files:
[2017-08-30T20:29:56.489+0100] [glassfish 4.1] [SEVERE] [] [org.glassfish.jersey.message.internal.WriterInterceptorExecutor] [tid: _ThreadID=70 _ThreadName=http-listener-1(2)] [timeMillis: 1504121396489] [levelValue: 1000] [[MessageBodyWriter not found for media type=application/json, type=class java.util.ArrayList, genericType=class java.util.ArrayList.]]
However, the same behavior is not seen on IBM WebSphere Liberty Profile. In fact, no error is thrown and the List of books is successfully serialized to a JSON representation. Further investigation shows that Liberty Profile is far more forgiving of deviations from the specification. More detailed research is needed to fully document the difference between server implementations I have only look at GlassFish and Liberty Profile. However, as GlassFish is the reference implementation for Java EE my advice is to develop with reference to its expectations because all other servers implementations should at least conform to its requirements.
The source code for this article is in the readlearncode_articles GitHub repository.
Further Reading
I regularly blog about Java EE on my blog readlearncode.com where I have recently published a mini-series of articles on the JAX-RS API.
Among the articles, there are discussions on bean validation failure in REST endpoints, how to work with Consumers and Producers, and how to create JAX-RS Resource Entities.
Do you want to know all the ways the @Context (javax.ws.rs.core.context) annotation can be used within your JAX-RS application. If so take a look at this five-parts series:
- What is javax.ws.rs.core.context? [ Part 1 ]
- What is javax.ws.rs.core.context? [ Part 2 ]
- What is javax.ws.rs.core.context? [ Part 3 ]
- What is javax.ws.rs.core.context? [ Part 4 ]
- What is javax.ws.rs.core.context? [ Part 5 ]
You can also return List directly in your method if you Book class has the JAXB annotations in place
Totally true. You do this…
@XmlRootElement
public class Book { .... }
and then in the endpoint class, you do this…
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Book> getAllBooks() {
List<Book> books = bookRepository.getAllBooks();
return books;
}
and the books List is serialized into JSON.
We have tips for Generic DAO, GenericEntity…?