Home > Blockchain >  Change default "type" for problem detail API in Spring Boot?
Change default "type" for problem detail API in Spring Boot?

Time:02-01

I'm using Spring Boot 3.x and I have a controller defined like this:

@RestController
@RequestMapping(path = ["/my-controller"])
@Validated
class MyController {
    private val log = loggerFor<MyController>()

    @PutMapping("/{something}", consumes = [APPLICATION_JSON_VALUE])
    @ResponseStatus(code = HttpStatus.NO_CONTENT)
    fun test(
        @PathVariable("something") something: String,
        @Valid @RequestBody someDto: SomeDTO
    ) {
        log.info("Received $someDto")
    }
}

data class SomeDTO(val myBoolean: Boolean)

I've also enabled problem details (RFC 7807) in my application.yaml file:

spring:
  mvc:
    problemdetails:
      enabled: true

When I make a request (in this example I'm using rest assured) to /my-controller/hello with a json body that (intentionally) doesn't match the expected data (myBoolean is not a valid boolean):

Given {
    port(<port>)
    contentType(JSON)
    body("""{ "myBoolean" : "not a boolean"}""")
    log().all()
} When {
    put("/my-controller/hello")
} Then {
    log().all().
    statusCode(400)
}

Then the response body looks like this:

{
    "type": "about:blank",
    "title": "Bad Request",
    "status": 400,
    "detail": "Failed to read request",
    "instance": "/my-controller/hello"
}

My question is, how can I change the default type from about:blank to something else?

CodePudding user response:

You need a @ControllerAdvice defined as follows:

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

  @ExceptionHandler(Exception.class)
  public ResponseEntity<Problem> handleAllExceptions(Exception ex, WebRequest request) {
    return ResponseEntity.of(
              Optional.of(
                Problem.builder()
                  .withType(URI.create("https://foobar.com/problem-definitions/blah"))
                  .withTitle("Bad Request")
                  .withDetail(ex.getMessage())
                  .withStatus(Status.BAD_REQUEST)
                  .build()
              ));  
    }

}

It returns this for your example:

{
    "type": "https://foobar.com/problem-definitions/blah",
    "title": "Bad Request",
    "status": 400,
    "detail": "Type definition error: [simple type, class com.example.demo.web.LanguageController$SomeDTO]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.demo.web.LanguageController$SomeDTO`: non-static inner classes like this can only by instantiated using default, no-argument constructor\n at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 3]"
}

I used this dependency:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>problem-spring-web-starter</artifactId>
    <version>0.28.0-RC.0</version>
</dependency>

Note that ProblemDetail is Spring Framework 6. Implementation in Spring 6 looks like this:

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ProblemDetail> handleAllExceptions(Exception ex, WebRequest request) {
    ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, ex.getMessage());
    problemDetail.setType(URI.create("https://foobar.com/problem-definitions/blah"));
    problemDetail.setInstance(URI.create("https://instance"));
    return ResponseEntity.of(Optional.of(problemDetail));
}
  • Related