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));
}