TODO
Error messages generated by ErrorMessageRestExceptionHandler
meets the Problem Details for HTTP APIs
specification.
For an example, the following error message that describes validation exception.
In JSON format:
{
"type": "http://example.org/errors/validation-failed",
"title": "Validation Failed",
"status": 422,
"detail": "The content you've send contains 2 validation errors.",
"errors": [
{
"field": "title",
"message": "must not be empty"
},
{
"field": "quantity",
"rejected": -5,
"message": "must be greater than zero"
}
]
}
… or in XML:
<problem>
<type>http://example.org/errors/validation-failed</type>
<title>Validation Failed</title>
<status>422</status>
<detail>The content you've send contains 2 validation errors.</detail>
<errors>
<error>
<field>title</field>
<message>must not be empty</message>
</error>
<error>
<field>quantity</field>
<rejected>-5</rejected>
<message>must be greater than zero</message>
</error>
</errors>
</problem>
Message values are read from a properties file through the provided MessageSource, so it can be simply customized
and localized. Library contains a default messages.properties file that is implicitly set as a parent (i.e. fallback)
of the provided message source. This can be disabled by setting withDefaultMessageSource
to false (on a builder or
factory bean).
The key name is prefixed with a fully qualified class name of the Java exception, or default
for the default value;
this is used when no value for a particular exception class exists (even in the parent message source).
Value is a message templates that may contain SpEL expressions delimited by #{
and }
. Inside an expression, you
can access the exception being handled and the current request under the ex
, resp. req
variables.
For an example:
org.springframework.web.HttpMediaTypeNotAcceptableException.type=http://httpstatus.es/406
org.springframework.web.HttpMediaTypeNotAcceptableException.title=Not Acceptable
org.springframework.web.HttpMediaTypeNotAcceptableException.detail=\
This resource provides only #{ex.supportedMediaTypes}, but you've sent Accept #{req.getHeaderValues('Accept')}.
@EnableWebMvc
@Configuration
public class RestContextConfig extends WebMvcConfigurerAdapter {
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add( exceptionHandlerExceptionResolver() ); // resolves @ExceptionHandler
resolvers.add( restExceptionResolver() );
}
@Bean
public RestHandlerExceptionResolver restExceptionResolver() {
return RestHandlerExceptionResolver.builder()
.messageSource( httpErrorMessageSource() )
.defaultContentType(MediaType.APPLICATION_JSON)
.addErrorMessageHandler(EmptyResultDataAccessException.class, HttpStatus.NOT_FOUND)
.addHandler(MyException.class, new MyExceptionHandler())
.build();
}
@Bean
public MessageSource httpErrorMessageSource() {
ReloadableResourceBundleMessageSource m = new ReloadableResourceBundleMessageSource();
m.setBasename("classpath:/org/example/messages");
m.setDefaultEncoding("UTF-8");
return m;
}
@Bean
public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver() {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.setMessageConverters(HttpMessageConverterUtils.getDefaultHttpMessageConverters());
return resolver;
}
}
<bean id="compositeExceptionResolver"
class="org.springframework.web.servlet.handler.HandlerExceptionResolverComposite">
<property name="order" value="0" />
<property name="exceptionResolvers">
<list>
<ref bean="exceptionHandlerExceptionResolver" />
<ref bean="restExceptionResolver" />
</list>
</property>
</bean>
<bean id="restExceptionResolver"
class="cz.jirutka.spring.web.servlet.exhandler.RestHandlerExceptionResolverFactoryBean">
<property name="messageSource" ref="httpErrorMessageSource" />
<property name="defaultContentType" value="application/json" />
<property name="exceptionHandlers">
<map>
<entry key="org.springframework.dao.EmptyResultDataAccessException" value="404" />
<entry key="org.example.MyException">
<bean class="org.example.MyExceptionHandler" />
</entry>
</map>
</property>
</bean>
<bean id="exceptionHandlerExceptionResolver"
class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver" />
<bean id="httpErrorMessageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource"
p:basename="classpath:/org/example/errorMessages"
p:defaultEncoding="UTF-8" />
The ExceptionHandlerExceptionResolver is used to resolve exceptions through @ExceptionHandler methods. It must be
registered before the RestHandlerExceptionResolver. If you don’t have any @ExceptionHandler methods, then you can
omit the exceptionHandlerExceptionResolver
bean declaration.
Builder and FactoryBean registers set of the default handlers by default. This can be disabled by setting
withDefaultHandlers
to false.
When the DispatcherServlet is unable to determine a corresponding handler for an incoming HTTP request, it sends 404
directly without bothering to call an exception handler (see
on StackOverflow). This behaviour can be changed, since
Spring version 4.0.0, using throwExceptionIfNoHandlerFound
init parameter. You should set this to true for a
consistent error responses.
When using WebApplicationInitializer:
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
protected void customizeRegistration(ServletRegistration.Dynamic reg) {
reg.setInitParameter("throwExceptionIfNoHandlerFound", "true");
}
...
}
…or classic web.xml:
<servlet>
<servlet-name>rest-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
...
</servlet>
Released versions are available in The Central Repository. Just add this artifact to your project:
<dependency>
<groupId>cz.jirutka.spring</groupId>
<artifactId>spring-rest-exception-handler</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
However if you want to use the last snapshot version, you have to add the Sonatype OSS repository:
<repository>
<id>sonatype-snapshots</id>
<name>Sonatype repository for deploying snapshots</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
This project is licensed under Apache License 2.0.