Building a REST API in Spring
In this tutorial, we will make a RESTful Web Service aka API in Spring framework. We will also setup the Controller and HTTP response codes, configuration of payload marshalling and content negotiation.
Spring framework is an open source Java platform that provides MVC infrastructure support for developing robust Java applications very easily and very rapidly using recommended MVC pattern.
Maven Dependency
To start, let’s add the required Maven dependencies to our project. We will be using Spring Web MVC related dependencies here.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
To check for latest dependencies available, see here.
What is REST programming?
REST is the underlying architectural principle of the web. The awesome thing about the web is the fact that clients (browsers) and servers can interact in complex ways without the client knowing anything about the server and the resources it hosts. The key rule is that the server and client must both agree on the media used, which in the case of the web is HTML.
An API that follows the principles of REST does not need the client to know anything about the structure of the API. Rather, the server needs to provide whatever information the client needs to interact with the API. An HTML form is an example of this: the server specifies the location of the resource, and the required fields. The browser doesn't know in advance where to submit the information, and it doesn't know in advance what information to submit. Both forms of information are entirely supplied by the server. (This principle is called HATEOAS.)
So, how does this apply to HTTP, and how can it be implemented in practice? HTTP is oriented around verbs and resources. The two verbs in mainstream usage are GET and POST, which I think everyone will recognize. However, the HTTP standard defines several others such as PUT and DELETE. These verbs are then applied to resources, according to the instructions provided by the server.
REST in Spring
Spring supports two ways to create RESTful services. These are:
- using MVC with ModelAndView
- using HTTP message converters
The ModelAndView approach is older, has better documentation, but is also more verbose and requires a lot of configuration. It tries to shoehorn the REST paradigm into the old model, which is not without problems. The Spring team understood this and provided first-class REST support starting with Spring 3.0.
The newer approach, based on HttpMessageConverter and annotations, is much more lightweight and easy to implement. Configuration is kept to a minimum. It is a little new on the documentation side. Nevertheless, this is the way RESTful services should be built after Spring 3.0.
Java Configuration
The most basic example of configuring Web MVC in our project can be seen as:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@EnableWebMvc
public class MvcConfig{
//
}
The @EnableWebMvc annotation is awesome, in the case of REST, it detects the existence of Jackson and JAXB 2 on the classpath and automatically creates and registers default JSON and XML converters.
When we need to change and configure those converters ourself, we should rather extend org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter.
The Controller
The @Controller is the central component in the entire Web Tier of the RESTful API. In this example, we expose a User resource.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping( value = "/user" )
class UserController{
@Autowired
UserService service;
@RequestMapping( method = RequestMethod.GET )
@ResponseBody
public List< User > findAll(){
return service.findAll();
}
@RequestMapping( value = "/{id}", method = RequestMethod.GET )
@ResponseBody
public User findOne( @PathVariable( "id" ) Long id ){
return RestPreconditions.checkFound( service.findOne( id ) );
}
@RequestMapping( method = RequestMethod.POST )
@ResponseStatus( HttpStatus.CREATED )
@ResponseBody
public Long create( @RequestBody User resource ){
Preconditions.checkNotNull( resource );
return service.create( resource );
}
@RequestMapping( value = "/{id}", method = RequestMethod.PUT )
@ResponseStatus( HttpStatus.OK )
public void update( @PathVariable( "id" ) Long id, @RequestBody User resource ){
Preconditions.checkNotNull( resource );
RestPreconditions.checkNotNull( service.getById( resource.getId() ) );
service.update( resource );
}
@RequestMapping( value = "/{id}", method = RequestMethod.DELETE )
@ResponseStatus( HttpStatus.OK )
public void delete( @PathVariable( "id" ) Long id ){
service.deleteById( id );
}
}
The Controller implementation is non-public because it receives HTTP requests from the Spring front controller, the DispathcerServlet and simply delegates them forward to a service layer. If there is no use case where the controller has to be injected or manipulated through a direct reference, then we should not declare it as public.
The request mappings are straightforward and as with any controller, the actual value of the mapping as well as the HTTP method are used to determine the target method for the request. @RequestBody will bind the parameters of the method to the body of the HTTP request, whereas @ResponseBody does the same for the response and return type. They also ensure that the resource will be marshalled and unmarshalled using the correct HTTP converter. Content negotiation will take place to choose which one of the active converters will be used, based mostly on the Accept header, although other HTTP headers may be used to determine the representation as well.
Mapping the HTTP response codes
Setting HTTP response codes is necessary and it can help to keep the services up and running. It can become complex and can also turn wrong if not done carefully.
Unmapped requests
If Spring MVC receives a request for which no mapping exists, it considers the request invalid and returns a 405 METHOD NOT ALLOWED back to the client. It is also good practice to include the Allow HTTP header when returning a 405 to the client, in order to specify which operations are allowed. This is the standard behavior of Spring MVC and does not require any additional configuration.
Valid, mapped requests
For any request that does have a mapping, Spring MVC considers the request valid and responds with 200 OK if no other status code is otherwise specified. It is because of this that the controller declares different @ResponseStatus for the create, UPDATE and DELETE actions but not for GET, which should indeed return the default 200 OK.
Client error
In case of a client error, custom exceptions are defined and mapped to the appropriate error codes. Simply throwing these exceptions from any of the layers of the web tier will ensure Spring maps the corresponding status code on the HTTP response.
@ResponseStatus( value = HttpStatus.BAD_REQUEST )
public class BadRequestException extends RuntimeException{
//
}
@ResponseStatus( value = HttpStatus.NOT_FOUND )
public class ResourceNotFoundException extends RuntimeException{
//
}
These exceptions are part of the REST API and as such, should only be used in the appropriate layers corresponding to REST; if for instance a DAO/DAL layer exist, it should not use the exceptions directly. Note also that these are not checked exceptions but runtime exceptions, in line with Spring practices and idioms.
Conclusion
This article shows how to implement and configure a REST Service using Spring framework 4 and a Java based configuration. We also covered HTTP response codes, basic Content Negotiation, and marshaling.
It is important to get the web services right and introduce 100% REST behavior in them to ensure they perform fast and error-free.
Recent Stories
Top DiscoverSDK Experts
Compare Products
Select up to three two products to compare by clicking on the compare icon () of each product.
{{compareToolModel.Error}}
{{CommentsModel.TotalCount}} Comments
Your Comment