Icon for gfsd IntelliJ IDEA

Comparing @Controller vs @RestController in Spring Boot

Learn the basics of @Controller and @RestController in Spring Boot

In this post, we cover the topic of controllers in Spring Boot, and see how @Controller and @RestController stack up.

Spring Boot is a popular extension to the Spring framework. If you are building web applications using Spring or Spring Boot, you have likely bumped into the notion of Controllers and RestControllers, which are two distinct concepts in the Spring framework. It is also a common question in job interviews to test your knowledge of Spring Boot.

🐣 New to Spring Boot?

Check out our introduction to Spring Boot!

This post will cover the differences between @Controller and @RestController in the context of Spring Boot. We’ll also provide examples of testing individual controller actions.

What are controllers in Spring Boot?

Per Spring’s MVC (Model-View-Controller) design pattern, controllers are used to handle HTTP requests and to return HTTP responses to the client. In the context of Spring, a controller is a class responsible for preparing a model map.

Besides the data to be displayed, controllers also choose the right view to display said data. To handle controllers in a Spring Boot application, you’ll use either the @Controller or the @RestController annotation.

🌱 Dive deeper into the details of Spring

Looking to get a deeper understanding of the Spring framework? Read our post that explains the differences between Spring vs Spring Boot vs Spring Web MVC vs Spring WebFlux.

And here’s our guide to mocking in Spring Boot.

Differences between @Controller vs @RestController in Spring Boot

There is a difference in how these two handle client requests, which also determines in what situation you should use each one:

  • @Controller: used to declare common web controllers that can return HTTP responses
  • @RestController: used to create controllers for REST APIs that can return JSON responses.

If you’re looking to handle requests from a human client, you’ll want to generate a final HTML view and therefore will use @Controller. If your client is a computer, you’ll most likely want to return an XML or a JSON instead of HTML, so you’ll use @RestController.

Let’s dive into the details of both annotations:

Spring's class hierarchy | Source: https://docs.symflower.com/docs/test-templates/spring-boot-test-template-examples/
Image source: https://docs.symflower.com/docs/test-templates/spring-boot-test-template-examples/

How to use @Controller in Spring Boot?

@Controller is a long-time Spring annotation that has always been used to define an entrance point for a Spring web application.

In the context of Spring applications, handling incoming requests happens via a handler method in a class annotated with @Controller. Using the @Controller annotation helps Spring auto-detect annotated classes through classpath scanning. Note that @Controller is only used on classes.

@Controller
public class BookController {
   ...

   @GetMapping("/books/most-popular")
   public String getMostPopularBookByISBN() {...}
}

@Controller is best used in UI-based applications where it’s likely that you’ll want to return a view (e.g. an HTML page).

In case you don’t need view resolution and rendering via an HTML template (e.g. if your client is not a user, as is the case with most REST API calls), you’ll need a solution that writes directly to the response body. That’s what @ResponseBody was for in earlier versions of Spring Boot.

Before Spring 4.0 (when @RestController was introduced), if you wanted to handle a REST request with @Controller, you had to combine it with @ResponseBody. Supported at the class as well as the method levels, @ResponseBody is used to indicate that the return value should be bound to the web response body and no view resolver is needed. Using the @ResponseBody annotation with @Controller, a controller can also write directly into the response stream to complete the request.

In versions Spring 4.0 and lower, here’s how you would have used this solution:

@Controller
@ResponseBody
public class MVCControllerWithResponseBody { 
   
}

Since handling responses for REST in the form of a JSON or XML (without rendering HTML) is a common use case, Spring developers came up with a solution: @RestController.

How to use @RestController in Spring Boot?

Introduced in Spring 4.0, @RestController is a specialized @Controller annotation. @RestController is best used when you want to return data (e.g. JSON or XML) rather than a view (HTML). @RestController is basically a meta-annotation that combines @Controller and @ResponseBody. Therefore, you don’t have to use both annotations together: the following two snippets of code will achieve the same thing in Spring Boot:

@Controller
@ResponseBody
public class MVCControllerWithResponseBody { 
   .. your logic
}
@RestController
public class  MVCControllerWithResponseBody { 
  .... your logic
}

One neat thing about @ResponseBody (and therefore, @RestController) is that it supports reactive types, so you can return Reactor or RxJava types along with their asynchronous values.

Controller testing in Spring Boot

There are three ways to test controllers in a Spring Boot environment. You can choose to use:

  • @MockMvc alone: Doesn’t load a Spring context which could be a good thing if your goal is to save resources. But this also limits the usability of standalone MockMvc to inside-server tests. This option needs more manual setup since you’ll be setting up the MockMVC manually and will explicitly configure the Controller under test.
  • @MockMvc + @WebMvcTest: Needs less setup because this option includes a partial Spring controller context (only the web layer is loaded). No manual configuration is needed as you can use annotations like @Autowired or @MockBean.
  • @SpringBootTest: Loads the entire Spring context. This is a heavyweight approach that leads to “fat” tests – overall, it’s ideal for integration testing rather than unit testing.

Due to the above limitations, in the following, we’ll only cover the option of using the combination of @MockMvc and @WebMvcTest. For a deeper dive into testing with Spring Boot, check out our blog post:

👀 Curious about more testing tips for Spring Boot?

Best practices for testing Spring Boot applications: a complete guide

Using @MockMvc with @WebMvcTest to test Spring Boot controllers

Our production code for this example is a simple Book controller that has a get mapping for returning the ISBN of the most popular book:

@Controller
public class BookController {
   ...

   @GetMapping("/books/most-popular")
   public String getMostPopularBookByISBN() {...}
}

Symflower generates the following test template that can be readily used to refine the expected behavior of getMostPopularBookByISBN:

@RunWith(SpringRunner.class)
@WebMvcTest(BookController.class)
public class BookControllerTest {
   @Autowired
   private MockMvc mockMvc;

   ...

   @Test
   public void getMostPopularBookByISBN() throws Exception {
       this.mockMvc.perform(get("/books/most-popular"))
           .andExpect(status().isOk())
           .andExpect(view().name(""))
           .andExpect(content().string(""));
   }
}

Next, let’s take a look at a put request which might have the following signature:

@PutMapping("/update-book")
public void updateBook(Book book) {
}

The test generated by Symflower then looks as follows:

@Test
public void updateBook() throws Exception {
   this.mockMvc.perform(put("/books/update-book")
           .param("id", "<value>")
           .param("isbn", "<value>")
           .param("author", "<value>")
           .param("title", "<value>")
           .param("yearPublished", "<value>"))
       .andExpect(status().isOk())
       .andExpect(content().string(""));
}

After this, all you need to do is provide meaningful values within the controller test!

Take a look at our documentation for more examples of what types of tests Symflower can generate for you.

Make sure you never miss any of our upcoming content by signing up for our newsletter and by following us on Twitter, LinkedIn or Facebook!

| 2024-03-16