
Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:
Once the early-adopter seats are all used, the price will go up and stay at $33/year.
Last updated: January 23, 2025
In this tutorial, we’ll learn how to set up REST in Spring, including the Controller and HTTP response codes, configuration of payload marshalling, and content negotiation.
To create a REST API when using Spring Boot, we need the Spring Boot Starter Web dependency, which bundles libraries for building web applications, handling HTTP requests, and JSON serialization: Simply include the following dependency in our pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Spring Boot automatically sets up Jackson as the default serializer and deserializer for converting between Java objects and JSON.
The @RestController is the central artifact in the entire Web Tier of the RESTful API. For the purpose of this article, the controller is modeling a simple REST resource, Foo:
@RestController
@RequestMapping("/foos")
class FooController {
@Autowired
private IFooService service;
@GetMapping
public List<Foo> findAll() {
return service.findAll();
}
@GetMapping(value = "/{id}")
public Foo findById(@PathVariable("id") Long id) {
return RestPreconditions.checkFound(service.findById(id));
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Long create(@RequestBody Foo resource) {
Preconditions.checkNotNull(resource);
return service.create(resource);
}
@PutMapping(value = "/{id}")
@ResponseStatus(HttpStatus.OK)
public void update(@PathVariable( "id" ) Long id, @RequestBody Foo resource) {
Preconditions.checkNotNull(resource);
RestPreconditions.checkNotNull(service.getById(resource.getId()));
service.update(resource);
}
@DeleteMapping(value = "/{id}")
@ResponseStatus(HttpStatus.OK)
public void delete(@PathVariable("id") Long id) {
service.deleteById(id);
}
}
As we can see, we’re using a straightforward, Guava-style RestPreconditions utility:
public class RestPreconditions {
public static <T> T checkFound(T resource) {
if (resource == null) {
throw new MyResourceNotFoundException();
}
return resource;
}
}
The Controller implementation is non-public because it doesn’t need to be.
Usually, the controller is the last in the chain of dependencies. It receives HTTP requests from the Spring front controller (the DispatcherServlet) and simply delegates them forward to a service layer. If there’s no use case where the controller has to be injected or manipulated through a direct reference, then we may prefer not to declare it as public.
The request mappings are straightforward. As with any controller, the actual value of the mapping, as well as the HTTP method, determines 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.
The @RestController is a shorthand to include both the @ResponseBody and the @Controller annotations in our class.
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.
When testing a Spring Boot application, the process is much easier thanks to Spring Boot’s autoconfiguration and testing annotations.
If we want to test the full application context without starting the server, we can use the @SpringBootTest annotation.
With that in place, we can then add the @AutoConfigureMockMvc to inject a MockMvc instance and send HTTP requests:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class FooControllerAppIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
public void whenTestApp_thenEmptyResponse() throws Exception {
this.mockMvc.perform(get("/foos")
.andExpect(status().isOk())
.andExpect(...);
}
}
To test only the web layer and avoid loading unnecessary parts of the application, we can use the @WebMvcTest annotation:
@RunWith(SpringRunner.class)
@WebMvcTest(FooController.class)
public class FooControllerWebLayerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private IFooService service;
@Test()
public void whenTestMvcController_thenRetrieveExpectedResult() throws Exception {
// ...
this.mockMvc.perform(get("/foos")
.andExpect(...);
}
}
By using @WebMvcTest, we focus only on testing the MVC layer, with Spring Boot automatically setting up the context for just the controller layer and dependencies (like mock services).
The status codes of the HTTP response are one of the most important parts of the REST service, and the subject can quickly become very complicated. Getting these right can be what makes or breaks the service.
If Spring MVC receives a request which doesn’t have a mapping, it considers the request not allowed, and returns a 405 METHOD NOT ALLOWED back to the client.
It’s also good practice to include the Allow HTTP header when returning a 405 to the client to specify which operations are allowed. This is the standard behavior of Spring MVC and doesn’t require any additional configuration.
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’s 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.
In the 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(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
//
}
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
//
}
These exceptions are part of the REST API, and as such, we should only use them in the appropriate layers corresponding to REST; for instance, if a DAO/DAL layer exists, it shouldn’t use the exceptions directly.
Note also that these aren’t checked exceptions, but runtime exceptions in line with Spring practices and idioms.
Another option to map custom exceptions on specific status codes is to use the @ExceptionHandler annotation in the controller. The problem with that approach is that the annotation only applies to the controller in which it’s defined. This means that we need to declare them in each controller individually.
Of course, there are more ways to handle errors in both Spring and Spring Boot that offer more flexibility.
This article illustrated how to implement and configure a REST Service using Spring and Java-based configuration.
In the next articles in the series, we’ll focus on the Discoverability of the API, advanced content negotiation, and working with additional representations of a Resource.
Follow the Spring Category