@ControllerAdvice vs @RestControllerAdvice

In this post, I would like to walk you through the differences between @ControllerAdvice and @RestControllerAdvice.
A featured image for category: Spring

1. Introduction

In this blog post I would like to explain the differences between @ControllerAdvice and @RestControllerAdvice, because oftentimes, these two lead to confusion.

On the other hand, if you are not sure what are these two used for, then I recommend you to check out my previous article, where you can learn how to handle exceptions efficiently when creating Spring Boot REST API with @RestControllerAdvice and @ExceptionHandler.

2. What Is @ControllerAdvice?

Let’s start by describing what exactly the @ControllerAdvice is?

Basically, it’s been introduced in Spring 3.2 and is a specialized @Component that allows us to declare @ExceptionHandler, @InitBinder, or @ModelAttribute methods to be shared among @Controller classes.

To put it another way, we can treat it as an annotation driven interceptor, whose methods will apply to the whole application (not just to an individual controller).

3. What Is @RestControllerAdvice?

On the other hand, the @RestControllerAdvice is a syntactic sugar for @ControllerAdvice and @ResponseBody annotations.

To illustrate, let’s see it’s definition:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice

Basically, the @ResponseBody indicates a method return value should be bound to the web response body. To put it simply, it allows us to implement methods without wrapping them with ResponseEntity<T> (or HttpEntity<T>):

fun withResponseEntity(): ResponseEntity<SomeClass> =
  ResponseEntity.ok(
    SomeClass()
  )

@ResponseBody
fun withResponseBody(): SomeClass =
  SomeClass()

Additionally, since Spring 4.0, it can be used on the type level, so it will be inherited by all methods of an annotated class.

4. @ControllerAdvice vs @RestControllerAdvice in Practice

With all of that being said, let’s analyze a few examples to better understand these differences.

Firstly, let’s create the Exceptions.kt file with a few custom exceptions:

class CustomExceptionOne : RuntimeException()
class CustomExceptionTwo : RuntimeException()
class CustomExceptionThree : RuntimeException()
class CustomExceptionFour : RuntimeException()
class CustomExceptionFive : RuntimeException()

Nextly, let’s add the ExampleResponse class carrying messages for API consumers:

data class ExampleResponse(val message: String)

Finally, let’s implement the ExampleController class:

@RestController
class ExampleController {

  @GetMapping("/one")
  fun endpointOne(): ExampleResponse {
    throw CustomExceptionOne()
    return ExampleResponse("Message")
  }

  @GetMapping("/two")
  fun endpointTwo(): ExampleResponse {
    throw CustomExceptionTwo()
    return ExampleResponse("Message")
  }

  @GetMapping("/three")
  fun endpointThree(): ExampleResponse {
    throw CustomExceptionThree()
    return ExampleResponse("Message")
  }

  @GetMapping("/four")
  fun endpointFour(): ExampleResponse {
    throw CustomExceptionFour()
    return ExampleResponse("Message")
  }

  @GetMapping("/five")
  fun endpointFive(): ExampleResponse {
    throw CustomExceptionFive()
    return ExampleResponse("Message")
  }

}

These are just a few test endpoints, which will be helpful to better understand @ControllerAdvice and @RestControllerAdvice in our examples.  Whatsoever, we can clearly see that each method throws its own, custom exception before returning ExampleResponse instance.

Without a doubt, querying any of these endpoints at this point will result in 500 Internal Server Error.

5. @ControllerAdvice Examples

5.1. Plain @ControllerAdvice

Undoubtedly, we don’t want our clients to see this type of responses and we should handle such cases in our code.

As the first step, let’s create the ErrorResponse, which will be used to return information for properly handled exceptions:

data class ErrorResponse(val message: String)

Following, let’s create the ExampleControllerAdvice:

@ControllerAdvice
class ExampleControllerAdvice {

  @ExceptionHandler(CustomExceptionOne::class)
  fun handleExceptionOne(): ErrorResponse =
    ErrorResponse("Handled exception one!")

}

As we can see, the class is marked as a @ControllerAdvice. Moreover, the method annotated with @ExceptionHandler should be invoked each time a CustomExceptionOne is thrown returning ErrorResponse instance.

Let’s check this assumption with cURL command:

curl localhost:8080/one

# Console Output:
javax.servlet.ServletException: Circular view path [one]...

# Response Body: 
{
  "timestamp": "2022-01-01T01:01:01.839+00:00",
  "status": 500,
  "error": "Internal Server Error",
  "path": "/one"
}

Well, this is not the response we were expecting. We can clearly see, that the exception was not caught properly leading to the ServletException.

5.2. @ControllerAdvice with ResponseEntity

But how can we solve this issue?

We’ve got a few possibilities, so let’s see the first one:

@ExceptionHandler(CustomExceptionTwo::class)
fun handleExceptionTwo(): ResponseEntity<ErrorResponse> =
  ResponseEntity.ok(
    ErrorResponse("Handled exception two!")
  )

This time, instead of returning ErrorResponse instance, we wrap it with the ResponseEntity<T>.

Let’s test our dedicated endpoint then:

curl localhost:8080/two

# Console Output:
empty

# Response Body: 
{
  "message": "Handled exception two!"
}

Obviously, this time handler works as expected returning the desired message. Whatsoever, empty console output indicates that no exception remain unhandled in this case.

5.3. @ResponseBody On Method Level

On the other hand, if we don’t want to return ResponseEntity, we can simply put @ResponseBody annotation to work:

@ExceptionHandler(CustomExceptionThree::class)
@ResponseBody
fun handleExceptionThree(): ErrorResponse =
  ErrorResponse("Handled exception three!")

Similarly, let’s check this handler:

curl localhost:8080/three

# Console Output:
empty

# Response Body: 
{
  "message": "Handled exception three!"
}

Again, the exception is handled perfectly.

5.4. @ResponseBody On Class Level

Although annotating methods with @ResponseBody is necessary in some cases, this approach would be better oftentimes.

To avoid unnecessary redundancy, we can simply put this annotation at the class level and it will be applied to all methods:

@ControllerAdvice
@ResponseBody
class ExampleControllerAdviceAnnotated {

  @ExceptionHandler(CustomExceptionFour::class)
  fun handleExceptionFour(): ErrorResponse =
    ErrorResponse("Handled exception four!")
}

Let’s check again:

curl localhost:8080/four

# Console Output:
empty

# Response Body: 
{
  "message": "Handled exception four!"
}

Similarly, we can see desired message in the response body.

6. @RestControllerAdvice Example

And finally we come to the point,that @RestControllerAdvice is a @ControllerAdvice with @ResponseBody (just like above).

Let’s implement our last example:

@RestControllerAdvice
class ExampleRestControllerAdvice {

  @ExceptionHandler(CustomExceptionFive::class)
  fun handleExceptionFive(): ErrorResponse =
    ErrorResponse("Handled exception five!")
}

Following, let’s query the necessary endpoint:

curl localhost:8080/five

# Console Output:
empty

# Response Body: 
{
  "message": "Handled exception five!"
}

Identically, the exception has been caught successfully by the dedicated handler.

7. @ControllerAdvice vs @RestControllerAdvice Summary

And that would be all for this comparison article on @ControllerAdvice vs @RestControllerAdvice. I believe, that after reading it you will have no more doubts about the differences between these two.

As always, if you would like to see the source code for this post, please check out this GitHub repository.

Finally, if you enjoy this type of content, please let me know in the comments section below, or by the contact form.

 

Previous articles, you might be interested in:

Share this:

Related content

Newsletter
Image presents 3 ebooks with Java, Spring and Kotlin interview questions.

Never miss any important updates from the Kotlin world and get 3 ebooks!

You may opt out any time. Terms of Use and Privacy Policy