Jersey and JSON examples (Jackson)
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)
Table of contents
- 1. Jackson as the JSON provider in Jersey
- 2. Project Directory
- 3. Project dependencies
- 4. Jersey endpoints and return a JSON response
- 5. Start Jersey application
- 6. DEMO
- 7. Custom Jackson object mapper in Jersey
- 8. Custom JsonMappingException in Jersey
- 9. Unit test a Jersey JSON endpoints
- 10. Download Source Code
- 11. References
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.
<!-- 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.
<?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.mkyong</groupId>
<artifactId>jersey-json-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</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.mkyong.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
.
package com.mkyong.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, "mkyong"),
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.
package com.mkyong.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
.
package com.mkyong;
import com.mkyong.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.
$ curl http://localhost:8080/json
{"result":"Jersey JSON example using Jackson 2.x"}
$ curl http://localhost:8080/json/mkyong
{"id":1,"name":"mkyong"}
$ curl http://localhost:8080/json/all
[{"id":1,"name":"mkyong"},{"id":2,"name":"zilap"}]
# send a POST request with JSON data
$ curl -H "Content-Type: application/json"
-X POST -d "{\"id\" : 1,\"name\" : \"mkyong\"}" 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.
package com.mkyong.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
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.
$ curl http://localhost:8080/json/java
{
"id" : 1,
"name" : "java"
}
$ curl http://localhost:8080/json/all
[ {
"id" : 1,
"name" : "mkyong"
}, {
"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.
$ curl -H "Content-Type: application/json"
-X POST -d "{\"id-error-field\" : 1,\"name\" : \"mkyong\"}" http://localhost:8080/json/create
Unrecognized field "id-error-field" (class com.mkyong.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.mkyong.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.
package com.mkyong.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
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.
$ curl -H "Content-Type: application/json"
-X POST -d "{\"id-error-field\" : 1,\"name\" : \"mkyong\"}" 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.
package com.mkyong;
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/mkyong")
.request(MediaType.APPLICATION_JSON)
.get(String.class);
// convert json string to JSONObject
JSONObject actual = new JSONObject(response);
String expected = "{\"id\":1,\"name\":\"mkyong\"}";
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\":\"mkyong\"},{\"id\":2,\"name\":\"zilap\"}]";
JSONAssert.assertEquals(expected, actual, false);
}
@Test
public void testJsonCreateOk() throws JSONException {
String json = "{\"id\":1,\"name\":\"mkyong\"}";
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\":\"mkyong\"}";
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);
}
}
10. Download Source Code
$ git clone https://github.com/mkyong/jax-rs
$ cd jax-rs/jersey/jersey-json-jackson/
$ mvn package
$ java -jar target/jersey-json-jackson.jar
11. References
- Eclipse Jersey
- Jersey and JSON support
- Jersey and HK2 dependency injection (auto scanning)
- Jersey 3x Latest User Guide
- JSONassert – Write JSON unit tests in less code
- Jackson – How to enable pretty print JSON output
- Jackson – Tree Model examples
- cURL – POST request examples
- Jackson – How to parse JSON
Below class are not being resolved, I checked the classpath and it is there, I even can see the source code for below classes, maven build is success but IDE is somehow not able to resolve it.
I tried invalidate cache and restart the idea but it does not help.
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;