Problem Details for HTTP APIs with Spring Boot


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:

  1. The Content-Type of the problem details should be application/problem+json. This will tell client how to properly parse the error response object
  2. The type is a URI that identifies the problem type. User can go to the URI to learn more about the problem
  3. The title is used to give a short, human-readable summary of the problem type
  4. The status is for the HTTP status code returned by the server
  5. The detail is a human readable explanation specific to this occurrence of the problem
  6. The instance is 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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s