This article shows how to return a JSON response in the Jersey application, using Jackson 2.x.
Tested with
- Jersey 3.0.2
- Grizzly 3 HTTP Server
- Jackson 2.12.2
- Java 11
- Maven
- JUnit 5 and JSONassert 1.5 (Unit Test)
1. Jackson as the JSON provider in Jersey
We can include jersey-media-json-jackson to enable Jackson as the JSON provider in the Jersey application.
pom.xml
<!-- add jackson as json provider --> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jackson</artifactId> </dependency>
2. Project Directory
Review the Maven standard project directory.
3. Project dependencies
Review the project dependencies.
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.favtuts</groupId> <artifactId>jersey-json-jackson-example</artifactId> <packaging>jar</packaging> <version>1.0</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <java.version>11</java.version> <junit.version>5.4.0</junit.version> <jsonassert.version>1.5.0</jsonassert.version> <jersey.version>3.0.2</jersey.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.glassfish.jersey</groupId> <artifactId>jersey-bom</artifactId> <version>${jersey.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- Grizzly 2 HTTP Server --> <dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jersey-container-grizzly2-http</artifactId> </dependency> <!-- Jersey DI and core--> <dependency> <groupId>org.glassfish.jersey.inject</groupId> <artifactId>jersey-hk2</artifactId> </dependency> <!-- add jackson as json provider --> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jackson</artifactId> </dependency> <!-- Need this to hide warning for jakarta.activation.DataSource --> <dependency> <groupId>jakarta.activation</groupId> <artifactId>jakarta.activation-api</artifactId> <version>2.0.1</version> </dependency> <!-- JUnit 5 --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <!-- test json data --> <dependency> <groupId>org.skyscreamer</groupId> <artifactId>jsonassert</artifactId> <version>${jsonassert.version}</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>jersey-json-jackson-example</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> <!-- JUnit 5 need at least 2.22.0 to support --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0-M5</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.0</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass>com.favtuts.MainApp</mainClass> </manifest> </archive> </configuration> </plugin> <!-- copy project dependencies --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>3.1.2</version> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <!-- exclude junit, we need runtime dependency only --> <includeScope>runtime</includeScope> <outputDirectory>${project.build.directory}/lib/</outputDirectory> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
4. Jersey endpoints and return a JSON response
Create a few endpoints in Jersey, and Jackson will handle the object from/to JSON conversion.
4.1 Create the following endpoints and return JSON response.
- GET
/json/
, returns a JSON string. - GET
/json/{name}
, returns anUser
object containg the{name}
in JSON string. - GET
/json/all
, returns a list ofUser
objects in JSON string. - POST
/json/create
, accepts JSON data and returns a status201
.
JsonResource.java
package com.favtuts.json; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import java.util.Arrays; import java.util.List; @Path("/json") public class JsonResource { private static final ObjectMapper mapper = new ObjectMapper(); @GET @Produces(MediaType.APPLICATION_JSON) public Response hello() { // create a JSON string ObjectNode json = mapper.createObjectNode(); json.put("result", "Jersey JSON example using Jackson 2.x"); return Response.status(Response.Status.OK).entity(json).build(); } // Object to JSON @Path("/{name}") @GET @Produces(MediaType.APPLICATION_JSON) public User hello(@PathParam("name") String name) { return new User(1, name); } // A list of objects to JSON @Path("/all") @GET @Produces(MediaType.APPLICATION_JSON) public List<User> helloList() { return Arrays.asList( new User(1, "favtuts"), new User(2, "zilap") ); } @Path("/create") @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response create(User user) { ObjectNode json = mapper.createObjectNode(); json.put("status", "ok"); return Response.status(Response.Status.CREATED).entity(json).build(); } }
4.2 Below is the User
object; later, Jackson will convert this object to/from a JSON string.
User.java
package com.favtuts.json; public class User { private int id; String name; public User() { } public User(int id, String name) { this.id = id; this.name = name; } // getters and setters }
5. Start Jersey application
Starts the Jersey application at http://localhost:8080
.
MainApp.java
package com.favtuts; import com.favtuts.json.JsonResource; import org.glassfish.grizzly.http.server.HttpServer; import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; import org.glassfish.jersey.server.ResourceConfig; import java.io.IOException; import java.net.URI; import java.util.logging.Level; import java.util.logging.Logger; public class MainApp { public static final URI BASE_URI = URI.create("http://localhost:8080"); // Starts Grizzly HTTP server public static HttpServer startHttpServer() { final ResourceConfig config = new ResourceConfig(); config.register(JsonResource.class); // JacksonFeature for JAXB/POJO, for pure JSON, no need this JacksonFeature // config.register(JacksonFeature.class); return GrizzlyHttpServerFactory.createHttpServer(BASE_URI, config); } public static void main(String[] args) { try { final HttpServer server = startHttpServer(); server.start(); // shut down hook Runtime.getRuntime().addShutdownHook(new Thread(server::shutdownNow)); System.out.println( String.format("Application started.%nStop the application using CTRL+C")); // block and wait shut down signal, like CTRL+C Thread.currentThread().join(); } catch (InterruptedException | IOException ex) { Logger.getLogger(MainApp.class.getName()).log(Level.SEVERE, null, ex); } } }
6. DEMO
Starts the MainApp
and using the cURL
tool to do simple testing.
Terminal
$ curl http://localhost:8080/json
{"result":"Jersey JSON example using Jackson 2.x"}
$ curl http://localhost:8080/json/favtuts
{"id":1,"name":"favtuts"}
$ curl http://localhost:8080/json/all
[{"id":1,"name":"favtuts"},{"id":2,"name":"zilap"}]
# send a POST request with JSON data
$ curl -H "Content-Type: application/json"
-X POST -d "{\"id\" : 1,\"name\" : \"favtuts\"}" http://localhost:8080/json/create
{"status":"ok"}
7. Custom Jackson object mapper in Jersey
In the above demo, the JSON response is in compact mode, the default behavior of Jackson ObjectMapper
; and we can create a custom Jackson ObjectMapper
in Jersey application by implementing the JAX RS ContextResolver
class.
7.1 Enable the Jackson pretty print mode
Below example, create a custom Jackson ObjectMapper
to enable the pretty print mode.
CustomJacksonMapperProvider.java
package com.favtuts.json; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import jakarta.ws.rs.ext.ContextResolver; import jakarta.ws.rs.ext.Provider; // enable Jackson pretty print @Provider public class CustomJacksonMapperProvider implements ContextResolver<ObjectMapper> { final ObjectMapper mapper; public CustomJacksonMapperProvider() { // enable pretty print mapper = new ObjectMapper() .enable(SerializationFeature.INDENT_OUTPUT); } @Override public ObjectMapper getContext(Class<?> type) { return mapper; } }
7.2 Register custom Jackson object mapper in ResourceConfig
MainApp.java
public static HttpServer startHttpServer() { final ResourceConfig config = new ResourceConfig(); config.register(JsonResource.class); // register the custom Jackson mapper config.register(CustomJacksonMapperProvider.class); return GrizzlyHttpServerFactory.createHttpServer(BASE_URI, config); }
7.3 Demo with pretty print enabled
Starts the MainApp
again, and now the JSON response is in a pretty print string.
Terminal
$ curl http://localhost:8080/json/java
{
"id" : 1,
"name" : "java"
}
$ curl http://localhost:8080/json/all
[ {
"id" : 1,
"name" : "favtuts"
}, {
"id" : 2,
"name" : "zilap"
} ]
8. Custom JsonMappingException in Jersey
We try to POST
an unrecognized field to /json/create
, and Jersey will return a detailed default message.
Terminal
$ curl -H "Content-Type: application/json"
-X POST -d "{\"id-error-field\" : 1,\"name\" : \"favtuts\"}" http://localhost:8080/json/create
Unrecognized field "id-error-field" (class com.favtuts.json.User), not marked as ignorable (2 known properties: "id", "name"])
at [Source: (org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream); line: 1, column: 22]
(through reference chain: com.favtuts.json.User["id-error-field"])
8.1 Custom JsonMappingException
We can create a custom ExceptionMapper
to intercept the JsonMappingException
error and return our custom JSON response.
CustomJsonExceptionMapper.java
package com.favtuts.json; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; @Provider public class CustomJsonExceptionMapper implements ExceptionMapper<JsonMappingException> { private static final ObjectMapper mapper = new ObjectMapper(); @Override public Response toResponse(JsonMappingException exception) { ObjectNode json = mapper.createObjectNode(); //json.put("error", exception.getMessage()); json.put("error", "json mapping error"); return Response.status(Response.Status.BAD_REQUEST) .entity(json.toPrettyString()) .build(); } }
8.2 Register custom JsonMappingException in ResourceConfig
MainApp.java
public static HttpServer startHttpServer() { final ResourceConfig config = new ResourceConfig(); config.register(JsonResource.class); config.register(CustomJacksonMapperProvider.class); // custom ExceptionMapper config.register(CustomJsonExceptionMapper.class); return GrizzlyHttpServerFactory.createHttpServer(BASE_URI, config); }
8.3 DEMO with custom JsonMappingException
Starts the MainApp
again, and we POST
an unrecognized field to /json/create
again, and now Jersey will return our custom JSON response.
Terminal
$ curl -H "Content-Type: application/json"
-X POST -d "{\"id-error-field\" : 1,\"name\" : \"favtuts\"}" http://localhost:8080/json/create
{
"error" : "json mapping error"
}
9. Unit test a Jersey JSON endpoints
Below is a JUnit 5 and JSONAssert example to test the Jersey JSON endpoints.
JsonResourceTest.java
package com.favtuts; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.glassfish.grizzly.http.server.HttpServer; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; import static org.junit.jupiter.api.Assertions.assertEquals; public class JsonResourceTest { private static HttpServer server; private static WebTarget target; @BeforeAll public static void beforeAllTests() { server = MainApp.startHttpServer(); Client c = ClientBuilder.newClient(); target = c.target(MainApp.BASE_URI.toString()); } @AfterAll public static void afterAllTests() { server.shutdownNow(); } @Test public void testJson() throws JSONException { String actual = target.path("json").request().get(String.class); String expected = "{\"result\":\"Jersey JSON example using Jackson 2.x\"}"; JSONAssert.assertEquals(expected, actual, false); } @Test public void testJsonName() throws JSONException { String response = target.path("json/favtuts") .request(MediaType.APPLICATION_JSON) .get(String.class); // convert json string to JSONObject JSONObject actual = new JSONObject(response); String expected = "{\"id\":1,\"name\":\"favtuts\"}"; JSONAssert.assertEquals(expected, actual, false); } @Test public void testJsonAll() throws JSONException { String response = target.path("json/all") .request(MediaType.APPLICATION_JSON) .get(String.class); // convert json string to JSONArray JSONArray actual = new JSONArray(response); String expected = "[{\"id\":1,\"name\":\"favtuts\"},{\"id\":2,\"name\":\"zilap\"}]"; JSONAssert.assertEquals(expected, actual, false); } @Test public void testJsonCreateOk() throws JSONException { String json = "{\"id\":1,\"name\":\"favtuts\"}"; Response response = target.path("json/create") .request(MediaType.APPLICATION_JSON) .post(Entity.entity(json, MediaType.valueOf("application/json"))); assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); // read response body String actual = response.readEntity(String.class); String expected = "{\"status\":\"ok\"}"; JSONAssert.assertEquals(expected, actual, false); } @Test public void testJsonCreateError() throws JSONException { String json = "{\"id_no_field\":1,\"name\":\"favtuts\"}"; Response response = target.path("json/create") .request(MediaType.APPLICATION_JSON) .post(Entity.entity(json, MediaType.valueOf("application/json"))); assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); String actual = response.readEntity(String.class); String expected = "{\"error\":\"json mapping error\"}"; JSONAssert.assertEquals(expected, actual, false); } }
Download Source Code
$ git clone https://github.com/favtuts/java-jax-rs-tutorials.git
$ cd jersey-json-jackson-example
$ mvn clean package
$ java -jar target/jersey-json-jackson-example.jar