If you are building REST APIs you will understand that HTTP status codes at times are not sufficient to convey enough information about an error. There is a specification RFC 7807 that defines how you can return error information back to the client. The way it works is by identifying a specific type of problem with a URI.
To make it more clear let’s suppose you are building a todo list application where you want to build API to move items from one list to other. If the target list has a cap on maximum items it can hold and you try to move an item from source list to target list the list then you should get an exception.
If all you have is HTTP status code then you can just return HTTP status code 403 forbidden. However, it does not give the API client enough information about why the request was forbidden. Different developers have solved this problem differently by building their application specific error objects. Problem Details for HTTP APIs specification solves this problem by defining a standard format in which API error response should be returned.
Below is an example JSON response built using the format defined by Problem Details for HTTP APIs specification.
HTTP/1.1 403 Forbidden Content-Type: application/problem+json Content-Language:en
{
"type":"https://mytodolistapp.com/probs/cant-move-item-to-list",
"title": "The target list is at maximum capacity",
"detail" : "The target list 'next-week' is full. It already has 10 items.",
"instance": "/lists/123/msgs/1"
}
Let’s understand the response shown above:
- The
Content-Typeof the problem details should beapplication/problem+json. This will tell client how to properly parse the error response object - The
typeis a URI that identifies the problem type. User can go to the URI to learn more about the problem - The
titleis used to give a short, human-readable summary of the problem type - The
statusis for the HTTP status code returned by the server - The
detailis a human readable explanation specific to this occurrence of the problem - The
instanceis a URI that identifies the specific occurrence of the problem
Problem details with Spring Boot
You will agree that problem details is useful. I am a Java developer and I mostly use Spring Boot for building REST APIs. I found that nice folks at Zolando have released an open source library problem that can be used to create problem detail objects. They also provide Spring bindings for the same.
Go to https://start.spring.io/ and create a Spring Boot application with only Spring Web dependency.
We will start by adding the following dependency to pom.xml
<dependency>
<groupId>org.zalando</groupId>
<artifactId>problem-spring-web-starter</artifactId>
<version>0.25.2</version>
</dependency>
Now, we will build a simple application that will return DemoNotWorkingException when we make GET request to /demo endpoint.
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.NativeWebRequest;
import org.zalando.problem.Problem;
import org.zalando.problem.Status;
import org.zalando.problem.StatusType;
import org.zalando.problem.ThrowableProblem;
import org.zalando.problem.spring.web.advice.ProblemHandling;
import java.net.URI;
import java.util.Optional;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@RestController
class DemoController {
@GetMapping(path = "/demo")
public String demo() {
throw new DemoNotWorkingException();
}
}
class DemoNotWorkingException extends RuntimeException {
}
@ControllerAdvice
class ApplicationExceptionHandler implements ProblemHandling {
@ExceptionHandler(DemoNotWorkingException.class)
public ResponseEntity<Problem> handleDemoNotWorkingException() {
return ResponseEntity.of(
Optional.of(
Problem.builder()
.withType(URI.create("https://mytodolistapp.com/probs/cant-move-item-to-list"))
.withTitle("The target list is at maximum capacity")
.withDetail("The target list 'next-week' is full. It already has 10 items.")
.withStatus(Status.FORBIDDEN)
.build()
));
}
}
As you can see above, Problem detail support in Spring is added using @ControllerAdvice mechanism of Spring web MVC.
We defined how in our method handleDemoNotWorkingException what problem detail object we want to return when DemoNotWorkingException is thrown. The Problem API provides a fluent API to build problem detail objects.
The response returned by the API is shown below.
{
"type": "https://mytodolistapp.com/probs/cant-move-item-to-list",
"title": "The target list is at maximum capacity",
"status": 403,
"detail": "The target list 'next-week' is full. It already has 10 items."
}
Conclusion
This was a quick post on how we can use problem details API to build meaningful error response objects that can help our clients learn better about the problems.
Discover more from Shekhar Gulati
Subscribe to get the latest posts sent to your email.
Good afternoon, what is the function of the DemoNotWorkingException class, it only extends RuntimeException ???