Reputation: 83
I've got some strange Error.
What I want to do: The client asks GET: /invoices/invoiceNumber with header Accept: application/pdf and I want to return PDF file. If client forgot about header I return HTTP 406.
The method that returns PDF bytes throws DocumentNotFoundException that is handled by Spring ExceptionHandler and should return 404, but it didn't. Instead of that, I've got 406 and server log:
2017-06-01 15:14:03.844 WARN 2272 --- [qtp245298614-13] o.e.jetty.server.handler.ErrorHandler : Error page loop /error
The same magic happens when Spring Security returns HTTP 401.
So I think that problem is that Client Accept application/pdf
but Spring ExceptionHandler returns application/json
, so jetty dispatcher override 404 with 406 :(
My code:
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Invoice not found")
@ExceptionHandler(DocumentNotFoundException.class)
public void handleException() {
//impl not needed
}
@GetMapping(value = "invoices/**", produces = MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<byte[]> getInvoicePdf(HttpServletRequest request) {
String invoiceNumber = extractInvoiceNumber(request);
final byte[] invoicePdf = invoiceService.getInvoicePdf(invoiceNumber);
return new ResponseEntity<>(invoicePdf, buildPdfFileHeader(invoiceNumber), HttpStatus.OK);
}
@GetMapping(value = "invoices/**")
public ResponseEntity getInvoiceOther() {
return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE);
}
Can someone help me with understanding that?
Upvotes: 5
Views: 3537
Reputation: 23660
To combat the infamous 406 issue, this is the solution I went with before the fix was released today (Jan 20, 2020) in Spring Boot 2.2.4.
I extended ResponseEntity<>
and forced it to always use JSON content type. Then I return this new instantiation in the global exception handler under the purview of the @Rest/ControllerAdvice
like so:
import org.jetbrains.annotations.Nullable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
public class JsonResponseEntity<T> extends ResponseEntity<T>
{
// You need an inner class or else you will run into super() issues
private static class Helper
{
private static MultiValueMap<String, String> headerHelper( @Nullable MultiValueMap<String, String> headers )
{
if ( headers == null )
{
headers = new HttpHeaders();
}
// The following is a generic version of: getHeaders().setContentType( MediaType.APPLICATION_JSON ); // NOSONAR
headers.set( CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8.toString() );
return headers;
}
}
public JsonResponseEntity( HttpStatus status )
{
this(null, Helper.headerHelper( null ), status);
}
public JsonResponseEntity( T body, HttpStatus status )
{
this(body, Helper.headerHelper( null ), status);
}
public JsonResponseEntity( MultiValueMap<String, String> headers, HttpStatus status )
{
super( Helper.headerHelper( headers ), status );
}
public JsonResponseEntity( T body, MultiValueMap<String, String> headers, HttpStatus status )
{
super( body, Helper.headerHelper( headers ), status );
}
}
Then in the expection handler you can return:
@ExceptionHandler(MyException.class)
public ResponseEntity<ErrorResponse> handleMyException(MyException e) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.NOT_FOUND, e.getLocalizedMessage()); // ErrorResponse is just a POJO
// You can do this:
// return ResponseEntity.status( 404 ).contentType( MediaType.APPLICATION_JSON ).body( errorResponse );
// or this:
return new JsonResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
}
By forcing the content type to JSON at the ResponseEntity
level, the 406 issue should dissapear.
Upvotes: 0
Reputation: 1040
The problem is that Spring tries to convert the error response to application/pdf
but fails to find a suitable HttpMessageConverter
which supports conversion to PDF.
The easiest solution is to manually create the error response:
@ExceptionHandler(DocumentNotFoundException.class)
public ResponseEntity<?> handleException(DocumentNotFoundException e) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body("{\"error\": \"Invoice not found\"}");
}
This bypasses message conversion and results in a HTTP 404 response code.
Upvotes: 2