Spring Boot JAX-RS Example
In this example, we will demonstrate how to build a JAX-RS web service with Spring Boot.
1. Introduction
REST (Representational
State Transfer) is an architectural pattern for developing web services. It is used in many web services development
projects because it is lightweight, stateless, and is therefore easily
scalable.
Java API for RESTful Web Services (or JAX-RS) is a Java EE API
specification for REST-style web
services. Specifically, the API provide
us with annotations for exposing POJOs as web resources. The annotations fall into the following
categories:
- URI Mapping for accessing resources.
- HTTP Methods for manipulating resources.
- Data Formats for producing and consuming the textual representations of resources.
- Request Parameters for binding parameters to Java types.
- Exceptions Mappers for catching application exceptions and returning custom HTTP responses.
We will cover some of these annotations in this article.
Spring Boot has excellent
support for JAX-RS web services. You generally have two JAX-RS implementations
to choose from:
- Jersey
- Apache CXF
In this example, we will show how to build a JAX-RS web service using Spring Boot and Apache CXF.
1.1 Tools Used in this Example
- Eclipse Java EE IDE for Java Developer 2018-12
- Spring Tools 4 – for Spring Boot
Spring Tools 4 for Spring Boot is a set of plugins for Eclipse that support building and running Spring Boot applications. You can add Spring Tools 4 to your existing Eclipse installation by going to the Eclipse Marketplace and searching for “Spring Tools 4”.
2. Spring Boot JAX-RS Example
In this example, we will build a simple student web service that exposes two read methods:
- getAllStudents – returns a collection of all the students in the data store.
- getById- returns a student specified by their id.
2.1 Create the Spring Boot Project
Let’s start by creating a Spring Boot project. In the New Project – Select a Wizard dialog box, expand Spring Boot and select Spring Starter Project. Click “Next”.
In the New Spring Starter Project dialog box, enter a name for the project. Also, enter the group, artifact and package information. Accept all the other default values. Click “Next”.
In the New Spring Starter Project Dependencies dialog box, select Spring Boot Version 2.1.3. and click “Finish”.
2.2 Add JAX-RS Dependencies
Next, we’ll add the Apache CXF JAX-RS starter dependency for Spring Boot. Open the pom.xml file and add the following just below the spring-boot-starter dependency.
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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.javacodegeeks.examples</groupId> <artifactId>spring-boot-jaxrs</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-boot-jaxrs</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxrs</artifactId> <version>3.3.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
The cxf-spring-boot-starter-jaxrs dependency provides the runtime environment for constructing and serving JAX-RS endpoints. It also provides the Java classes used to auto-discover JAX-RS root resources and providers. (More on this later.)
2.3 Create the Model
The entity that we will expose in our web service models a student. Create a new package com.javacodegeeks.examples.jaxrs.model
and class Student
with the following code:
Student.java
public class Student { Long id; String firstName; String lastName; String year; public Student(Long id, String firstName, String lastName, String year) { this.id = id; this.firstName = firstName; this.lastName = lastName; this.year = year; } public Long getId() { return id; } public void setId(Long 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 getYear() { return year; } public void setYear(String year) { this.year = year; } }
2.4 Create the Repository
In a real-world application, a repository interacts with a database using a datasource or entity manager. For the purposes of this example, we will use a java.util.Map
to store our data directly in the repository class.
Create a new package com.javacodegeeks.examples.jaxrs.repository
and class StudentRepository
with the following code:
StudentRepository.java
import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Optional; import javax.annotation.PostConstruct; import org.springframework.stereotype.Repository; import com.javacodegeeks.examples.jaxrs.model.Student; @Repository public class StudentRepository { Map<Long, Student> students = new HashMap<>(); @PostConstruct public void init() { students.put(101l, new Student(101l, "Jane", "Doe", "Junior")); students.put(102l, new Student(102l, "Martin", "Fowler", "Senior")); students.put(103l, new Student(103l, "Roy", "Fielding", "Freshman")); } public Collection<Student> findAll() { return students.values(); } public Optional<Student> findById(Long id){ Student student = null; if (students.containsKey(id)) student = students.get(id); return Optional.ofNullable(student); } }
The class is decorated with the @Repository
annotation to indicate it is a repository and to register it as a Spring Bean in the application context. The Map stores a key (the student’s ID) and the student object in each entry. We also initialize the data store with three students in the init() method. This method is executed after the class has been instantiated as it is decorated with @PostConstruct
.
The class has two read methods, one returns a collection of Student objects and the other returns a single Student specified by the id parameter.
We are using java.util.Optional
as a container for our Student object as it will help us handle cases where Student equals null. This is purely an
implementation decision.
2.5 Create the Root Resource Interface
Next, we’ll create the root resource interface. Create a new package com.javacodegeeks.examples.jaxrs.service
and interface StudentService
with the following code:
SudentService.java
import java.util.Collection; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import com.javacodegeeks.examples.jaxrs.model.Student; @Path("students") @Produces(MediaType.APPLICATION_JSON) public interface StudentService { @GET public Collection<Student> getAllStudents(); @Path("{id}") @GET public Response getById(@PathParam("id") Long id); }
The @Path
annotation identifies the class as a root resource. The Path’s value specifies the relative URI where the resource will accept requests. We are indicating that this root resource will respond to requests at “students”. If a URI path template is used, (e.g. @Path("students/{id}")
) the embedded variable, indicated by braces, is substituted at runtime with the value in the actual request. (For example, students/101.) Path annotations can be applied at the class or method level. If both are used, the method’s value is appended to that of the class, as is the case for the getById
method discussed below.
The @Produces
annotation declares the media type or types that can be returned in the response. Since we are specifying the media type at the class level, it will be applied to all methods of this class. (Note: We can also specify or override this value at the method level.) You will typically see JSON as the media type as it is prevalent in REST applications.
The @GET
annotation indicates that the method will respond to HTTP GET requests. Other annotations used to manipulate resources are @POST, @UPDATE, and @DELETE.
As discussed above, @Path can use a URI path template. In this case, a @PathParam
annotation is used to retrieve and bind the embedded variable to a class variable or method parameter. Here we are binding the {id} in the path to the Long id parameter of the getById
method .
The getById
method returns a javax.ws.rs.core.Response
object. The Response object is an abstraction of an HTTP response and allows you to include metadata, such as status codes, using the builder pattern.
2.6 Implement the Interface
Create a class named StudentServiceImpl
that implements StudentService using the following code:
SudentServiceImpl.java
import java.util.Collection; import javax.ws.rs.core.Response; import com.javacodegeeks.examples.jaxrs.exception.StudentNotFoundException; import com.javacodegeeks.examples.jaxrs.model.Student; import com.javacodegeeks.examples.jaxrs.repository.StudentRepository; public class StudentServiceImpl implements StudentService { private final StudentRepository repository; public StudentServiceImpl(StudentRepository repository) { this.repository = repository; } @Override public Collection<Student> getAllStudents() { return repository.findAll(); } @Override public Response getById(Long id) { Student student = repository.findById(id).orElseThrow(StudentNotFoundException::new); return Response.ok(student).build(); } }
The StudentRepository is initialized through the class constructor. The getAllStudents
method calls the repository’s findAll
method and returns a Collection of Student objects. The getById(Long id)
method calls the repository’s findById
method to retrieve a student. If no student is returned it will throw a StudentNotFoundException. Otherwise, it will return a Response which contains the student entity and an OK status.
You will see an error message that “StudentNotFoundException cannot be resolved to a type”. Let’s fix that.
2.7 Add a Custom Exception Class
Create a custom exception class for cases where the student is not found. Create a new package com.javacodegeeks.examples.jaxrs.exception
and class StudentNotFoundException
that extends RuntimeExcepton
:
StudentNotFoundException.java
public class StudentNotFoundException extends RuntimeException { }
2.8 Configure the Application
Configuring JAX-RS endpoints in Apache CXF is quite simple. Open application.properties and add the following properties:
application.properties
cxf.path=/studentservice cxf.jaxrs.classes-scan=true cxf.jaxrs.classes-scan-packages=com.javacodegeeks.examples.jaxrs
The cxf.path property is used to define the path for CXF services. The default is /services.
Setting the cxf.jaxrs.classes-scan property to true will inform Apache CXF to scan for classes that are decorated with the JAX-RS annotations @Path and @Provider in the packages listed by the cxf.jaxrs.classes-scan-packages property. In this example, Apache CXF will scan the com.javacodegeeks.examples.jaxrs package to configure root resources as JAX-RS endpoints.
2.10 Test the JAX-RS Web Service
Right-click your project in Project Explorer and select Run As > Spring Boot App. Open a web browser, enter http://localhost:8080/studentservice/students in the address bar and hit enter.
You will see the following error message: “No message body writer has been found for class java.util.HashMap$Values, ContentType: application/json“. It seems we are missing something. Let’s address the issue.
2.11 Add the JSON Provider Dependencies
The Apache CXF JAX-RS starter does not include a MessageBodyWriter
for the JSON content type. Luckily, JAX-RS allows us to plug in providers. You can roll your own provider or add a third-party provider. Let’s do the later and add the dependencies for the “Jackson JAX-RS Provider for JSON Content Type” to provide JSON MessageBodyReader and MessageBodyWriter support. Open the pom.xml file and add the following just below the cxf-spring-boot-starter-jaxrs dependency
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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.javacodegeeks.examples</groupId> <artifactId>spring-boot-jaxrs</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-boot-jaxrs</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxrs</artifactId> <version>3.3.1</version> </dependency> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-jaxrs</artifactId> <version>1.9.13</version> </dependency> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-xc</artifactId> <version>1.9.13</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2.12 Update the Configuration
Let’s add org.codehaus.jackson.jaxrs to the list of packages to scan. Apache CXF will then be able to find the Jackson JSON provider. Open application.properties and modify the cxf.jaxrs.classes-scan-packages property as follows:
application.properties
cxf.path=/studentservice cxf.jaxrs.classes-scan=true cxf.jaxrs.classes-scan-packages=com.javacodegeeks.examples.jaxrs,org.codehaus.jackson.jaxrs
Restart the application and re-run the test case. You will see that a MessageBodyWriter
is now being used to convert the Java types to JSON.
2.13 Add an ExceptionMapper
Let’s send a request for a non-existing student. Enter http://localhost:8080/studentservice/students/104 in the address bar and hit enter. This will throw our custom StudentNotFoundException
.
The page does not provide a meaningful error message. Also, the response is in HTML, which may pose a problem if the client is expecting JSON.
We can address both issues by using an ExceptionMapper
.
An ExceptionMapper class is used to catch application
exceptions for the purpose of writing custom HTTP responses. You define them by implementing the
ExceptionMapper
interface. Create a new
class StudentNotFoundExceptionMapper
with the following code:
StudentNotFoundExceptionMapper.java
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; import org.springframework.beans.factory.annotation.Value; @Provider public class StudentNotFoundExceptionMapper implements ExceptionMapper { @Value("${message.studentNotfound}") String message; @Override public Response toResponse(StudentNotFoundException exception) { return Response.serverError().entity(message).type(MediaType.APPLICATION_JSON).build(); } }
The ExceptionMapper
interface has one method, toResponse
, that takes an Exception as a parameter and maps it to a Response object. In this example, we are mapping the StudentNotFoundException
to a Response with a server error status and a custom message. (The custom message is injected into the class variable using the @Value annotation.) Also, notice that we are setting the response body to the JSON content type.
You also need to decorate the class with the @Provider annotation so that it can be auto-discovered by the JAX-RS runtime.
Finally, we need to add the custom message in the
application.properties file.
application.properties
cxf.path=/studentservice cxf.jaxrs.classes-scan=true cxf.jaxrs.classes-scan-packages=com.javacodegeeks.examples.jaxrs,org.codehaus.jackson.jaxrs message.studentNotfound={"error":"500","message":"Student does not exist"}
Restart the application and re-run the test case. You will see that the error page now has our custom message in JSON format.
3. Spring Boot JAX-RS – Summary
In this example, we demonstrated how to build a JAX-RS web service using Spring Boot and Apache CXF.
4. Download the Source Code
This was a Spring Boot JAX-RS Example.
You can download the full source code of this example here: Spring Boot JAX-RS Example
Nice article. However, 2 observations: 1. JAX-RS is also implemented by RestEasy, from RedHat. Furthermore, the biggest implementors are Jersey (Oracle; it comes with Glassfish and Payara platforms) and RestEasy (RedHat; it comes with JBoss and Wildfy platforms). I never heard about Apache CxF until today… maybe you should also mention RestEasy in the article. 2. It makes little sense to me to use JAX-RS with Spring, particularly since Spring also has a REST module on its own. Spring is a competitor to Java EE, it’s a framework on its own. Even though you can use them together, I generally… Read more »
Hello amihaiemil. Thank you for your comments. 1. I was remiss in not naming RESTEasy as one of the popular implementations of JAX-RS. Restlet also deserves mention. 2. Indeed, Spring does have its own REST module. In fact, I wrote and article last year that demonstrated how to create a REST API using Spring Boot and Spring framework API. You can find it here: https://examples.javacodegeeks.com/enterprise-java/spring/boot/spring-boot-rest-api-tutorial/. However, some shops like to stick with the JAX-RS spec for developing REST APIs and this article demonstrates how easy it is to stand up a JAX-RS compliant web service using Spring Boot and Apache… Read more »
I would like to clarify that the REST capabilities in Spring are part of the Spring Web MVC framework API.
Paypal is using Spring with JAX-RS as their internal stack for backend