Boot

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”.

Spring Boot JAX-RS - Select a Wizard
Select a Wizard

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”.

Spring Boot JAX-RS - New Spring Boot Project
New Spring Boot Project

In the New Spring Starter Project Dependencies dialog box, select Spring Boot Version 2.1.3. and click “Finish”.

Spring Boot JAX-RS - Spring Boot Version
Spring Boot Version

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.exceptionand 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.

Spring Boot JAX-RS - No Message Body Writer Found
No Message Body Writer Found

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.

Spring Boot JAX-RS - MessageBodyWriter for JSON
MessageBodyWriter for 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.

Spring Boot JAX-RS - Whitelabel Error Page
Whitelabel Error Page

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.

Spring Boot JAX-RS - ExceptionWrapper
Using an ExceptionWrapper

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.

Download
You can download the full source code of this example here: Spring Boot JAX-RS Example

Gilbert Lopez

Gilbert Lopez is an application developer and systems integration developer with experience building business solutions for large and medium-sized companies. He has worked on many Java EE projects. His roles have included lead developer, systems analyst, business analyst and consultant. Gilbert graduated from California State University in Los Angeles with a Bachelor of Science degree in Business.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
amihaiemil
5 years ago

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 »

charles
charles
3 years ago
Reply to  amihaiemil

Paypal is using Spring with JAX-RS as their internal stack for backend

Back to top button