Skip to content

Commit ed85658

Browse files
committed
Return 5xx HTTP status for invalid target types with Jackson
InvalidDefinitionException has been introduced in Jackson 2.9 to be able to differentiate invalid data sent from the client (should still generate a 4xx HTTP status code) from server side errors like beans with no default constructor (should generate a 5xx HTTP status code). Issue: SPR-14925
1 parent 454e61e commit ed85658

File tree

5 files changed

+85
-1
lines changed

5 files changed

+85
-1
lines changed

spring-web/src/main/java/org/springframework/http/converter/HttpMessageConversionException.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,29 @@
1717
package org.springframework.http.converter;
1818

1919
import org.springframework.core.NestedRuntimeException;
20+
import org.springframework.http.HttpStatus;
21+
22+
import java.util.Optional;
2023

2124
/**
2225
* Thrown by {@link HttpMessageConverter} implementations when a conversion attempt fails.
2326
*
2427
* @author Arjen Poutsma
28+
* @author Sebastien Deleuze
2529
* @since 3.0
2630
*/
2731
@SuppressWarnings("serial")
2832
public class HttpMessageConversionException extends NestedRuntimeException {
2933

34+
private final HttpStatus errorStatus;
35+
3036
/**
3137
* Create a new HttpMessageConversionException.
3238
* @param msg the detail message
3339
*/
3440
public HttpMessageConversionException(String msg) {
3541
super(msg);
42+
this.errorStatus = null;
3643
}
3744

3845
/**
@@ -42,6 +49,25 @@ public HttpMessageConversionException(String msg) {
4249
*/
4350
public HttpMessageConversionException(String msg, Throwable cause) {
4451
super(msg, cause);
52+
this.errorStatus = null;
4553
}
4654

55+
/**
56+
* Create a new HttpMessageConversionException.
57+
* @since 5.0
58+
* @param msg the detail message
59+
* @param cause the root cause (if any)
60+
* @param errorStatus the HTTP error status related to this exception
61+
*/
62+
public HttpMessageConversionException(String msg, Throwable cause, HttpStatus errorStatus) {
63+
super(msg, cause);
64+
this.errorStatus = errorStatus;
65+
}
66+
67+
/**
68+
* Return the HTTP error status related to this exception if any.
69+
*/
70+
public Optional<HttpStatus> getErrorStatus() {
71+
return Optional.ofNullable(errorStatus);
72+
}
4773
}

spring-web/src/main/java/org/springframework/http/converter/HttpMessageNotReadableException.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.http.converter;
1818

19+
import org.springframework.http.HttpStatus;
20+
1921
/**
2022
* Thrown by {@link HttpMessageConverter} implementations when the
2123
* {@link HttpMessageConverter#read} method fails.
@@ -43,4 +45,15 @@ public HttpMessageNotReadableException(String msg, Throwable cause) {
4345
super(msg, cause);
4446
}
4547

48+
/**
49+
* Create a new HttpMessageNotReadableException.
50+
* @since 5.0
51+
* @param msg the detail message
52+
* @param cause the root cause (if any)
53+
* @param errorStatus the HTTP error status related to this exception
54+
*/
55+
public HttpMessageNotReadableException(String msg, Throwable cause, HttpStatus errorStatus) {
56+
super(msg, cause, errorStatus);
57+
}
58+
4659
}

spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@
3636
import com.fasterxml.jackson.databind.ObjectWriter;
3737
import com.fasterxml.jackson.databind.SerializationConfig;
3838
import com.fasterxml.jackson.databind.SerializationFeature;
39+
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
3940
import com.fasterxml.jackson.databind.ser.FilterProvider;
4041
import com.fasterxml.jackson.databind.type.TypeFactory;
4142

4243
import org.springframework.core.ResolvableType;
4344
import org.springframework.http.HttpInputMessage;
4445
import org.springframework.http.HttpOutputMessage;
46+
import org.springframework.http.HttpStatus;
4547
import org.springframework.http.MediaType;
4648
import org.springframework.http.converter.AbstractGenericHttpMessageConverter;
4749
import org.springframework.http.converter.HttpMessageConverter;
@@ -235,6 +237,9 @@ private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
235237
}
236238
return this.objectMapper.readValue(inputMessage.getBody(), javaType);
237239
}
240+
catch (InvalidDefinitionException ex) {
241+
throw new HttpMessageNotReadableException("Could not read document: " + ex.getMessage(), ex, HttpStatus.INTERNAL_SERVER_ERROR);
242+
}
238243
catch (IOException ex) {
239244
throw new HttpMessageNotReadableException("Could not read document: " + ex.getMessage(), ex);
240245
}

spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@
3131
import com.fasterxml.jackson.databind.ser.FilterProvider;
3232
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
3333
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
34+
import org.junit.Assert;
3435
import org.junit.Test;
3536

3637
import org.springframework.core.ParameterizedTypeReference;
38+
import org.springframework.http.HttpStatus;
3739
import org.springframework.http.MediaType;
3840
import org.springframework.http.MockHttpInputMessage;
3941
import org.springframework.http.MockHttpOutputMessage;
@@ -380,6 +382,22 @@ public void writeSubTypeList() throws Exception {
380382
assertTrue(result.contains("\"number\":123"));
381383
}
382384

385+
@Test
386+
public void readWithNoDefaultConstructor() throws Exception {
387+
String body = "{\"property1\":\"foo\",\"property2\":\"bar\"}";
388+
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
389+
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
390+
try {
391+
converter.read(BeanWithNoDefaultConstructor.class, inputMessage);
392+
}
393+
catch (HttpMessageNotReadableException ex) {
394+
assertTrue(ex.getErrorStatus().isPresent());
395+
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, ex.getErrorStatus().get());
396+
return;
397+
}
398+
fail();
399+
}
400+
383401

384402
interface MyInterface {
385403

@@ -537,4 +555,25 @@ public void setProperty2(String property2) {
537555
}
538556
}
539557

558+
private static class BeanWithNoDefaultConstructor {
559+
560+
private final String property1;
561+
562+
private final String property2;
563+
564+
public BeanWithNoDefaultConstructor(String property1, String property2) {
565+
this.property1 = property1;
566+
this.property2 = property2;
567+
}
568+
569+
public String getProperty1() {
570+
return property1;
571+
}
572+
573+
public String getProperty2() {
574+
return property2;
575+
}
576+
577+
}
578+
540579
}

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.beans.ConversionNotSupportedException;
2828
import org.springframework.beans.TypeMismatchException;
2929
import org.springframework.core.Ordered;
30+
import org.springframework.http.HttpStatus;
3031
import org.springframework.http.MediaType;
3132
import org.springframework.http.converter.HttpMessageNotReadableException;
3233
import org.springframework.http.converter.HttpMessageNotWritableException;
@@ -354,7 +355,7 @@ protected ModelAndView handleHttpMessageNotReadable(HttpMessageNotReadableExcept
354355
if (logger.isWarnEnabled()) {
355356
logger.warn("Failed to read HTTP message: " + ex);
356357
}
357-
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
358+
response.sendError(ex.getErrorStatus().orElse(HttpStatus.BAD_REQUEST).value());
358359
return new ModelAndView();
359360
}
360361

0 commit comments

Comments
 (0)