This Tutorial will explain how to produce and consume JSON or XML in Java REST Services with Jersey and MOXy. MOXy is the default JSON-Binding Provider in Jersey 2.x and therefore also in GlassFish 4. However, I have experienced that Jackson is (slightly) faster than MOXy and it is a little easier to configure. For demonstration of how things work we will implement two different REST Services and two simple POJOs as our "model" classes. The REST services will produce and consume JSON and the JSON serialization and de-serialization happens automatically behind the scenes - no need for any extra annotations in the "model" classes. The same thing works also for XML serialization and de-serialization. However, I have disabled that feature to show you that JSON works with simple POJOs and without any additional annotations. XML will work out of the box as soon as you add a the @XmlRootElement annotation to the model classes and enable MediaType.APPLICATION_XML for the corresponding REST services.
You can compile the Maven project and run it on any Servlet Container which supports Servlet API 3.1, i.e. Tomcat 8 or Glassfish 4. Basically, this example even runs on any Servlet API 3.0 compliant Servlet Container, i.e. Tomcat 7 oder Glassfish 3. All you would need to do is changing the web.xml slightly - that's it (see comments).
If you are interested in using Jersey with Jackson then check my other tutorial.
Table of contents:
<?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <groupId>com.nabisoft.tutorials</groupId> <artifactId>tomcat-jersey-moxy-demo</artifactId> <version>1.0-SNAPSHOT</version> <name>tomcat-jersey-moxy-demo</name> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir> <version.jdk>1.8</version.jdk> <!-- 1.7 for JDK 7 --> <version.mvn.compiler>3.2</version.mvn.compiler> <version.mvn.war.plugin>2.6</version.mvn.war.plugin> <version.jersey>2.15</version.jersey> <version.servlet.api>3.1.0</version.servlet.api> <!-- use 3.0.1 for Tomcat 7 or Java EE 6 (i.e. Glassfish 3.x) --> </properties> <repositories> <repository> <id>java.net-Public</id> <name>Maven Java Net Snapshots and Releases</name> <url>https://maven.java.net/content/groups/public/</url> <layout>default</layout> </repository> <repository> <id>Central</id> <name>Maven Repository</name> <url>http://repo1.maven.org/maven2</url> <layout>default</layout> </repository> <repository> <id>central</id> <name>Central Repository</name> <url>http://repo.maven.apache.org/maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url> <id>eclipselink</id> <layout>default</layout> <name>Repository for library EclipseLink (JPA 2.0)</name> </repository> </repositories> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${version.servlet.api}</version> <scope>provided</scope> </dependency> <!-- Jersey --> <dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jersey-container-servlet</artifactId> <version>${version.jersey}</version> </dependency> <!-- do not use jettison, prefer jackson <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jettison</artifactId> <version>${version.jersey}</version> </dependency> --> <!-- do not use Jackson because moxy is jersey default <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jackson</artifactId> <version>${version.jersey}</version> </dependency> --> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-moxy</artifactId> <version>${version.jersey}</version> </dependency> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-processing</artifactId> <version>${version.jersey}</version> </dependency> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-multipart</artifactId> <version>${version.jersey}</version> </dependency> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-sse</artifactId> <version>${version.jersey}</version> </dependency> <!-- if you are using Jersey client specific features --> <dependency> <groupId>org.glassfish.jersey.core</groupId> <artifactId>jersey-client</artifactId> <version>${version.jersey}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${version.mvn.compiler}</version> <configuration> <source>${version.jdk}</source> <target>${version.jdk}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>${version.mvn.war.plugin}</version> <configuration> <failOnMissingWebXml>true</failOnMissingWebXml> <archive> <addMavenDescriptor>false</addMavenDescriptor> </archive> </configuration> </plugin> </plugins> </build> </project>
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> <!-- use this for Servlet API 3 (Tomcat 7, Glassfish 3.x) --> <!-- <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> --> <session-config> <session-timeout>30</session-timeout> <cookie-config> <name>SESSIONID</name> </cookie-config> </session-config> </web-app>
We will use two very simple POJOs. Make sure to add the @XmlRootElement annotation in case you also want to consume/produce XML. You could also add some JSON annotations (but this is not required).
package com.nabisoft.tutorials.jerseymoxy.model; import java.util.Date; //@XmlRootElement //only needed if we also want to generate XML public class Message { private String firstName; private String lastName; private int age; private Date date; private String text; 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; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public String getText() { return text; } public void setText(String text) { this.text = text; } }
package com.nabisoft.tutorials.jerseymoxy.model; import java.util.Map; //@XmlRootElement //only needed if we also want to generate XML public class Person { private String firstName; private String lastName; private String dateOfBirth; private String[] citizenships; private Map<String, Object> creditCards; private int age; 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; } public String getDateOfBirth() { return dateOfBirth; } public void setDateOfBirth(String dateOfBirth) { this.dateOfBirth = dateOfBirth; } public String[] getCitizenships() { return citizenships; } public void setCitizenships(String[] citizenships) { this.citizenships = citizenships; } public Map<String, Object> getCreditCards() { return creditCards; } public void setCreditCards(Map<String, Object> creditCards) { this.creditCards = creditCards; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
Our two REST Services are very simple as well. Make sure to add MediaType.APPLICATION_XML in case you also want to consume/produce XML.
package com.nabisoft.tutorials.jerseymoxy.jaxrs.resource; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import com.nabisoft.tutorials.jerseymoxy.model.Message; @Path("/message") public class MessageResource { @GET @Path("ping") public String getServerTime() { System.out.println("RESTful Service 'MessageService' is running ==> ping"); return "received ping on "+new Date().toString(); } @GET @Produces({MediaType.APPLICATION_JSON}) //add MediaType.APPLICATION_XML if you want XML as well (don't forget @XmlRootElement) public List<Message> getAllMessages() throws Exception{ List<Message> messages = new ArrayList<>(); Message m = new Message(); m.setDate(new Date()); m.setFirstName("Nabi"); //m.setLastName("Zamani"); m.setAge(30); m.setText("Hello World!"); messages.add(m); System.out.println("getAllMessages(): found "+messages.size()+" message(s) on DB"); return messages; //do not use Response object because this causes issues when generating XML automatically } @POST @Consumes({MediaType.APPLICATION_JSON}) @Produces({MediaType.TEXT_PLAIN}) @Path("/post") public String postMessage(Message msg) throws Exception{ System.out.println("First Name = "+msg.getFirstName()); System.out.println("Last Name = "+msg.getLastName()); return "ok"; } }
package com.nabisoft.tutorials.jerseymoxy.jaxrs.resource; import java.util.HashMap; import java.util.Map; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import com.nabisoft.tutorials.jerseymoxy.model.Person; @Path("/person") public class PersonResource { @Path("get") @GET @Produces({MediaType.APPLICATION_JSON}) //add MediaType.APPLICATION_XML if you want XML as well (don't forget @XmlRootElement) public Person getPerson(){ Person p = new Person(); p.setFirstName("Nabi"); p.setLastName("Zamani"); //p.setDateOfBirth("01.01.2012"); p.setCitizenships( new String[]{"German", "Persian"} ); Map<String, Object> creditCards = new HashMap<String, Object>(); creditCards.put("MasterCard", "1234 1234 1234 1234"); creditCards.put("Visa", "1234 1234 1234 1234"); creditCards.put("dummy", true); p.setCreditCards(creditCards); System.out.println("REST call..."); //return Response.ok().entity(p).build(); return p; } @POST @Consumes({MediaType.APPLICATION_JSON}) @Produces({MediaType.TEXT_PLAIN}) @Path("/post") public String postPerson(Person pers) throws Exception{ System.out.println("First Name = "+pers.getFirstName()); System.out.println("Last Name = "+pers.getLastName()); return "ok"; } }
Now we make sure to use MOXy.
package com.nabisoft.tutorials.jerseymoxy.jaxrs.provider; import java.util.HashMap; import java.util.Map; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Provider; import org.glassfish.jersey.moxy.json.MoxyJsonConfig; /** * @see https://jersey.java.net/documentation/latest/user-guide.html#d0e6509 * @author nabizamani * */ @Provider //@Produces(MediaType.APPLICATION_JSON) //@Consumes(MediaType.APPLICATION_JSON) //@Singleton public class JsonMoxyConfigurationContextResolver implements ContextResolver<MoxyJsonConfig> { private final MoxyJsonConfig config; public JsonMoxyConfigurationContextResolver() { final Map<String, String> namespacePrefixMapper = new HashMap<String, String>(); namespacePrefixMapper.put("http://www.w3.org/2001/XMLSchema-instance", "xsi"); config = new MoxyJsonConfig() .setNamespacePrefixMapper(namespacePrefixMapper) .setNamespaceSeparator(':') // .setAttributePrefix("") // .setValueWrapper("value") // .property(JAXBContextProperties.JSON_WRAPPER_AS_ARRAY_NAME, true) .setFormattedOutput(false) .setIncludeRoot(false) .setMarshalEmptyCollections(false); } @Override public MoxyJsonConfig getContext(Class<?> objectType) { return config; } }
Now we need to wire everything together by registering the relevant classes.
package com.nabisoft.tutorials.jerseymoxy.jaxrs.application; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/service") public class ApplicationConfig extends Application { @Override public Set<Class<?>> getClasses() { Set<Class<?>> resources = new java.util.HashSet<>(); System.out.println("REST configuration starting: getClasses()"); //features //this will register MOXy JSON providers resources.add(org.glassfish.jersey.moxy.json.MoxyJsonFeature.class); //we could also use this //resources.add(org.glassfish.jersey.moxy.xml.MoxyXmlFeature.class); //instead let's do it manually: resources.add(com.nabisoft.tutorials.jerseymoxy.jaxrs.provider.JsonMoxyConfigurationContextResolver.class); resources.add(com.nabisoft.tutorials.jerseymoxy.jaxrs.resource.MessageResource.class); resources.add(com.nabisoft.tutorials.jerseymoxy.jaxrs.resource.PersonResource.class); //==> we could also choose packages, see below getProperties() System.out.println("REST configuration ended successfully."); return resources; } @Override public Set<Object> getSingletons() { return Collections.emptySet(); } @Override public Map<String, Object> getProperties() { Map<String, Object> properties = new HashMap<>(); //in Jersey WADL generation is enabled by default, but we don't //want to expose too much information about our apis. //therefore we want to disable wadl (http://localhost:8080/service/application.wadl should return http 404) //see https://jersey.java.net/nonav/documentation/latest/user-guide.html#d0e9020 for details properties.put("jersey.config.server.wadl.disableWadl", true); //we could also use something like this instead of adding each of our resources //explicitely in getClasses(): //properties.put("jersey.config.server.provider.packages", "com.nabisoft.tutorials.mavenstruts.service"); return properties; } }
The demo page offers links to directly access the services. Check your console when clicking any of the links or the buttons. The buttons demonstrate how to consume JSON in a REST Service, the links will produce JSON.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Pojo to Json Serialization using Jersey with MOXy for Java REST Services</title> <script src="<%=request.getContextPath() %>/js/jquery-1.11.2.min.js"></script> <script> var ctxPath = "<%=request.getContextPath() %>"; $(function(){ $("#postPerson, #postMessage").on("click", function(){ $.ajax({ url: $(this).attr("id") === "postMessage" ? ctxPath+"/service/message/post" : ctxPath+"/service/person/post", type: "POST", data: '{"firstName":"Michael", "lastName":"Jordan"}', contentType: "application/json", cache: false, dataType: "json" }); }); }); </script> </head> <body> <h1>Pojo to Json Serialization using Jersey with MOXy for Java REST Services</h1> <ul> <li><a href="<%=request.getContextPath() %>/service/message"><%=request.getContextPath() %>/service/message</a></li> <li><a href="<%=request.getContextPath() %>/service/message/ping"><%=request.getContextPath() %>/service/message/ping</a></li> <li><a href="<%=request.getContextPath() %>/service/person/get"><%=request.getContextPath() %>/service/person/get</a></li> <li><button id="postPerson">Post Person</button></li> <li><button id="postMessage">Post Message</button></li> </ul> </body> </html>