JSON has become the de-facto standard for data exchange in web services, and for Java developers, Gson is a highly popular library for converting Java objects to JSON and vice-versa. When building RESTful APIs with JAX-RS (specifically Jersey), integrating Gson can significantly streamline your development process. This post will guide you through setting up a Jersey project to leverage Gson for automatic JSON serialization and deserialization.
Why Gson with JAX-RS?
While JAX-RS implementations like Jersey often come with their own default JSON providers (like Jackson), Gson offers a lightweight and often more intuitive API for many developers. Its simple approach to serialization and deserialization, along with features like custom type adapters and versioning, makes it a compelling choice for many projects.
Project Setup: Maven Dependencies
To begin, you’ll need to include the necessary dependencies in your pom.xml file. We’ll need the Jersey server dependency and the Gson library itself.
<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.ankurm.blog</groupId>
<artifactId>jersey-gson-example</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<jersey.version>2.34</jersey.version> <!-- Or your desired Jersey version -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Jersey JAX-RS Server -->
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${jersey.version}</version>
</dependency>
<!-- Gson Library -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version> <!-- Or the latest stable version -->
</dependency>
<!-- For running embedded server (e.g., Jetty or Tomcat embedded) -->
<!-- Depending on your setup, you might need a servlet API dependency in development -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version> <!-- Or appropriate version >= 3.1 -->
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Creating a Custom Gson MessageBodyReader/Writer
Jersey, by default, doesn’t know how to use Gson for JSON handling. We need to create custom JAX-RS MessageBodyReader and MessageBodyWriter implementations that leverage Gson. These will inform Jersey how to read and write Java objects as JSON using Gson.
1. The Employee Model
Let’s define a simple Employee class that we’ll be serializing/deserializing.
package com.ankurm.blog.model;
public class Employee {
private int id;
private String firstName;
private String lastName;
private String email;
public Employee() {
}
public Employee(int id, String firstName, String lastName, String email) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
// Getters and Setters
public int getId() {
return id;
}
public void setId(int 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;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
'}';
}
}
2. GsonProvider: The Core Integration
This class will implement both MessageBodyReader and MessageBodyWriter. It will use a Gson instance to perform the actual JSON conversions.
package com.ankurm.blog.providers;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
@Provider // Register this as a JAX-RS provider
@Produces(MediaType.APPLICATION_JSON) // This provider produces JSON
@Consumes(MediaType.APPLICATION_JSON) // This provider consumes JSON
public class GsonProvider implements MessageBodyReader<Object>, MessageBodyWriter<Object> {
private final Gson gson;
public GsonProvider() {
// You can customize your Gson instance here, e.g., for pretty printing, date formats
gson = new GsonBuilder()
.setPrettyPrinting() // For pretty-printed JSON output
// .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") // Example date format
.create();
}
@Override
public boolean isReadable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
// This provider can read any class if the media type is JSON
return mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE);
}
@Override
public Object readFrom(Class<Object> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
try (InputStreamReader reader = new InputStreamReader(entityStream, "UTF-8")) {
return gson.fromJson(reader, genericType);
}
}
@Override
public boolean isWriteable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
// This provider can write any class if the media type is JSON
return mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE);
}
@Override
public void writeTo(Object object, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
throws IOException, WebApplicationException {
try (OutputStreamWriter writer = new OutputStreamWriter(entityStream, "UTF-8")) {
gson.toJson(object, genericType, writer);
}
}
@Override
public long getSize(Object object, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
// Deprecated by JAX-RS 2.0. Return -1 indicates that
// the message body writer is not able to determine the length in advance.
return -1;
}
}
Explanation of GsonProvider:
@Provider: This annotation tells Jersey to discover and register this class as a JAX-RS provider.@Produces(MediaType.APPLICATION_JSON)and@Consumes(MediaType.APPLICATION_JSON): These annotations specify that this provider handles JSON media types.isReadableandisWriteable: These methods determine if the provider can handle a given type and media type. Our implementation makes it generic for any class and JSON.readFrom: This method takes anInputStream(the request body), uses Gson to deserialize it into the target Java object, and returns the object.writeTo: This method takes a Java object, uses Gson to serialize it into JSON, and writes it to theOutputStream(the response body).getSize: This method is deprecated and can return -1.
The JAX-RS Resource
Now, let’s create a simple JAX-RS resource that exposes endpoints for our Employee model. Jersey will automatically use our GsonProvider for JSON conversions.
package com.ankurm.blog.resources;
import com.ankurm.blog.model.Employee;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.List;
@Path("/employees")
public class EmployeeResource {
private static List<Employee> employees = new ArrayList<>();
static {
employees.add(new Employee(1, "Ankur", "M", "[email protected]"));
employees.add(new Employee(2, "Jane", "Doe", "[email protected]"));
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getAllEmployees() {
return Response.ok(employees).build();
}
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response getEmployeeById(@PathParam("id") int id) {
Employee employee = employees.stream()
.filter(e -> e.getId() == id)
.findFirst()
.orElse(null);
if (employee != null) {
return Response.ok(employee).build();
} else {
return Response.status(Response.Status.NOT_FOUND).entity("Employee not found").build();
}
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response addEmployee(Employee employee) {
if (employee.getId() == 0) { // Simple validation for new employee
employee.setId(employees.size() + 1); // Assign a new ID
}
employees.add(employee);
return Response.status(Response.Status.CREATED).entity(employee).build();
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response updateEmployee(Employee updatedEmployee) {
for (int i = 0; i < employees.size(); i++) {
if (employees.get(i).getId() == updatedEmployee.getId()) {
employees.set(i, updatedEmployee);
return Response.ok(updatedEmployee).build();
}
}
return Response.status(Response.Status.NOT_FOUND).entity("Employee to update not found").build();
}
@DELETE
@Path("/{id}")
public Response deleteEmployee(@PathParam("id") int id) {
boolean removed = employees.removeIf(e -> e.getId() == id);
if (removed) {
return Response.noContent().build();
} else {
return Response.status(Response.Status.NOT_FOUND).entity("Employee not found").build();
}
}
}
JAX-RS Application Configuration
Finally, we need to tell Jersey about our application classes and providers. We do this by extending ResourceConfig (or Application). Jersey will automatically discover classes annotated with @Path and @Provider if they are in the configured package(s).
package com.ankurm.blog.config;
import com.ankurm.blog.providers.GsonProvider;
import com.ankurm.blog.resources.EmployeeResource;
import org.glassfish.jersey.server.ResourceConfig;
import javax.ws.rs.ApplicationPath;
@ApplicationPath("/api") // Base path for all REST endpoints
public class MyApplication extends ResourceConfig {
public MyApplication() {
// Register JAX-RS resources and providers
// Option 1: Register classes directly
// register(EmployeeResource.class);
// register(GsonProvider.class);
// Option 2: Scan packages for resources and providers (recommended for larger apps)
packages("com.ankurm.blog.resources", "com.ankurm.blog.providers");
}
}
Deployment (web.xml)
For a traditional WAR deployment, you’ll configure Jersey in your web.xml to use your MyApplication class.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>Jersey Gson Example</display-name>
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.ankurm.blog.config.MyApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>
Testing the API
Deploy your WAR file to a servlet container (like Tomcat or Jetty). Assuming your context root is /jersey-gson-example and your ApplicationPath is /api, your base URL will be http://localhost:8080/jersey-gson-example/api.
You can use tools like Postman, Insomnia, or curl to test the endpoints:
1. Get All Employees
curl -X GET http://localhost:8080/jersey-gson-example/api/employees \
-H "Accept: application/json"
Expected Output:
[
{
"id": 1,
"firstName": "Ankur",
"lastName": "M",
"email": "[email protected]"
},
{
"id": 2,
"firstName": "Jane",
"lastName": "Doe",
"email": "[email protected]"
}
]
2. Get Employee by ID
curl -X GET http://localhost:8080/jersey-gson-example/api/employees/1 \
-H "Accept: application/json"
Expected Output:
{
"id": 1,
"firstName": "Ankur",
"lastName": "M",
"email": "[email protected]"
}
3. Add a New Employee
curl -X POST http://localhost:8080/jersey-gson-example/api/employees \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"firstName": "John",
"lastName": "Smith",
"email": "[email protected]"
}'
Expected Output (with new ID):
{
"id": 3,
"firstName": "John",
"lastName": "Smith",
"email": "[email protected]"
}
4. Update an Employee
curl -X PUT http://localhost:8080/jersey-gson-example/api/employees \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"id": 1,
"firstName": "Ankur",
"lastName": "M. Updated",
"email": "[email protected]"
}'
Expected Output:
{
"id": 1,
"firstName": "Ankur",
"lastName": "M. Updated",
"email": "[email protected]"
}
5. Delete an Employee
curl -X DELETE http://localhost:8080/jersey-gson-example/api/employees/2
Expected Output:
HTTP 204 No Content
Conclusion
By implementing a custom MessageBodyReader and MessageBodyWriter, you can seamlessly integrate Gson with your JAX-RS (Jersey) applications. This approach provides fine-grained control over Gson’s configuration and ensures that your RESTful services efficiently handle JSON data. Happy coding!