• Stars
    star
    354
  • Rank 116,178 (Top 3 %)
  • Language
    Java
  • License
    Apache License 2.0
  • Created about 10 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A convenient Spring MVC exception handler for RESTful APIs.

Spring REST Exception handler

Build Status Coverage Status Codacy code quality Maven Central

The aim of this project is to provide a convenient exception handler (resolver) for RESTful APIs that meets a best-practices for error responses without repeating yourself. It’s very easy to handle custom exceptions, customize error responses and even localize them. Also solves some pitfalls [1] in Spring MVC with a content negotiation when producing error responses.

Error message

Error messages generated by ErrorMessageRestExceptionHandler follows the Problem Details for HTTP APIs specification.

For example, the following error message describes a 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>

How does it work?

RestHandlerExceptionResolver

The core class of this library that resolves all exceptions is RestHandlerExceptionResolver. It holds a registry of RestExceptionHandlers.

When your controller throws an exception, the RestHandlerExceptionResolver will:

  1. Find an exception handler by the thrown exception type (or its supertype, supertype of the supertype… up to the Exception class if no more specific handler is found) and invoke it.

  2. Find the best matching media type to produce (using ContentNegotiationManager, utilises Accept header by default). When the requested media type is not supported, then fallback to the configured default media type.

  3. Write the response.

RestExceptionHandler

Implementations of the RestExceptionHandler interface are responsible for converting the exception into Spring’s ResponseEntity instance that contains a body, headers and a HTTP status code.

The main implementation is ErrorMessageRestExceptionHandler that produces the ErrorMessage body (see above for example). All the attributes (besides status) are loaded from a properties file (see the section Localizable error messages). This class also logs the exception (see the Exception logging section).

Configuration

Java-based configuration

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

XML-based configuration

<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.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" />

Another resolvers

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.

Default handlers

Builder and FactoryBean registers a set of the default handlers by default. This can be disabled by setting withDefaultHandlers to false.

Localizable error messages

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 template that may contain SpEL expressions delimited by #{ and }. Inside an expression, you can access the exception being handled and the current request (instance of HttpServletRequest) under the ex, resp. req variables.

For 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 #{ex.supportedMediaTypes}, but you've requested #{req.getHeader('Accept')}.

Exception logging

Exceptions handled with status code 5×× are logged on ERROR level (incl. stack trace), other exceptions are logged on INFO level without a stack trace, or on DEBUG level with a stack trace if enabled. The logger name is cz.jirutka.spring.exhandler.handlers.RestExceptionHandler and a Marker is set to the exception’s full qualified name.

Why is 404 bypassing exception handler?

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 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>

How to get it?

Released versions are available in jCenter and the Central Repository. Just add this artifact to your project:

Maven
<dependency>
    <groupId>cz.jirutka.spring</groupId>
    <artifactId>spring-rest-exception-handler</artifactId>
    <version>1.2.0</version>
</dependency>
Gradle
compile 'cz.jirutka.spring:spring-rest-exception-handler:1.2.0'

However if you want to use the last snapshot version, you have to add the JFrog OSS repository:

Maven
<repository>
    <id>jfrog-oss-snapshot-local</id>
    <name>JFrog OSS repository for snapshots</name>
    <url>https://oss.jfrog.org/oss-snapshot-local</url>
    <snapshots>
        <enabled>true</enabled>
    </snapshots>
</repository>
Gradle
repositories {
  maven {
    url 'https://oss.jfrog.org/oss-snapshot-local'
  }
}

Requirements

  • Spring 3.2.0.RELEASE and newer is supported, but 4.× is highly recommended.

  • Jackson 1.× and 2.× are both supported and optional.

License

This project is licensed under Apache License 2.0.


1. Nothing terrible, Spring MVC is still a far better then JAX-RS for RESTful APIs! ;)

More Repositories

1

rsql-parser

Parser for RSQL / FIQL – query language for RESTful APIs
Java
685
star
2

maven-badges

Badge for Maven Central
Ruby
240
star
3

ssh-ldap-pubkey

Utility to manage SSH public keys stored in LDAP.
Python
217
star
4

ldap-passwd-webui

Very simple web interface for changing password stored in LDAP or Active Directory (Samba 4 AD).
Python
198
star
5

luapak

Easily build a standalone executable for any Lua program
Lua
175
star
6

esh

Simple templating engine based on shell.
Shell
172
star
7

ngx-oauth

OAuth 2.0 proxy for nginx written in Lua.
Lua
156
star
8

validator-collection

The easiest way to validate collections of basic types using Bean Validation.
Java
120
star
9

smlar

PostgreSQL extension for an effective similarity search || mirror of git://sigaev.ru/smlar.git || see https://www.pgcon.org/2012/schedule/track/Hacking/443.en.html
C
110
star
10

tty-copy

Copy content to system clipboard via TTY and terminal using ANSI OSC52 sequence
C
89
star
11

setup-alpine

Easily use Alpine Linux on GitHub Actions, with support for QEMU user emulator
Shell
89
star
12

zsh-shift-select

Select text in Zsh command line using Shift, as in many text editors and GUI programs
Shell
88
star
13

validator-spring

Bean Validator utilizing Spring Expression Language (SpEL)
Java
84
star
14

asciidoctor-html5s

Semantic HTML5 converter (backend) for Asciidoctor
HTML
79
star
15

luasrcdiet

Compresses Lua source code by removing unnecessary characters (updated fork of http://luasrcdiet.luaforge.net/)
Lua
64
star
16

apk-autoupdate

Automatic updates for Alpine Linux and other systems using apk-tools
Shell
45
star
17

embedmongo-spring

Spring Factory Bean for “Embedded” MongoDB
Java
44
star
18

otf2bdf

OpenType to BDF Converter (unofficial mirror)
C
42
star
19

apcupsd-snmp

Apcupsd module for Net-SNMP
Perl
41
star
20

njs-typescript-starter

A starting template for developing njs (NGINX JavaScript) scripts for NGINX server in TypeScript.
JavaScript
40
star
21

nginx-binaries

Nginx and njs binaries for Linux (x86_64, aarch64, ppc64le), macOS and Windows. Linux binaries are static so works on every Linux.
TypeScript
35
star
22

doas-sudo-shim

sudo shim for doas
Shell
34
star
23

qemu-openrc

OpenRC init script for QEMU/KVM (for Gentoo and Alpine Linux)
Shell
30
star
24

rake-jekyll

Rake tasks for Jekyll as a gem.
Ruby
30
star
25

corefines

💎 A collection of refinements for Ruby core classes with a compatibility mode for older Rubies and a convenient syntactic sugar.
Ruby
26
star
26

user-aports

My Alpine Linux aports that are not in official repository yet or don’t adhere to Alpine polices (bundles)
Shell
26
star
27

akms

Alpine Kernel Module Support – aka DKMS for Alpine Linux
Shell
26
star
28

sh-parser

Parser of POSIX Shell Command Language
Lua
25
star
29

ipynb2html

Convert Jupyter (IPython) Notebooks to static HTML
TypeScript
24
star
30

asciidoctor-highlight.js

Asciidoctor.js extension for highlighting code in build time using Highlight.js
JavaScript
21
star
31

nginx-testing

Support for integration/acceptance testing of nginx configuration in TypeScript/JavaScript.
TypeScript
19
star
32

zzz

A simple program to suspend or hibernate your computer 💤
C
19
star
33

efi-mkuki

EFI Unified Kernel Image Maker
Shell
18
star
34

asciidoctor-rouge

Rouge code highlighter support for Asciidoctor (OBSOLETE)
Ruby
18
star
35

opensmtpd-filter-rewrite-from

OpenSMTPD 6.6+ filter for rewriting From address
Awk
17
star
36

asciidoctor-katex

Asciidoctor extension for converting latexmath using KaTeX at build time
Ruby
17
star
37

ssh-getkey-gitlab

A simple script to be used as AuthorizedKeysCommand in OpenSSH server to look up user’s public keys in GitLab or GitHub.
Shell
17
star
38

babel-preset-njs

A Babel preset for njs - NGINX JavaScript
JavaScript
16
star
39

unidecode

Transliteration from Unicode to US-ASCII and ISO 8859-2.
Java
14
star
40

haste-client

CLI client for haste-server (hastebin.com) written in Python
Python
13
star
41

ts-transformer-inline-file

A TypeScript custom transformer for inlining files
TypeScript
12
star
42

swaylockd

A dumb launcher to spawn swaylock and ensure it runs no matter what
C
11
star
43

ssh-getkey-ldap

A simple script to be used as AuthorizedKeysCommand in OpenSSH server to look up user’s public keys in LDAP.
Lua
11
star
44

alpine-zsh-config

A sensible system-wide Zsh configuration for Alpine Linux
Shell
11
star
45

argp

Rust derive-based argument parsing optimized for code size and flexibility
Rust
11
star
46

stunnel-static

stunnel built as a fully static binary
Shell
10
star
47

ansible-gentoo-roles

A curated list of Ansible roles for Gentoo Linux.
10
star
48

asciidoctor-include-ext

Asciidoctor’s standard include processor reimplemented as an extension
Ruby
9
star
49

rsql-hibernate

This project is outdated. Use https://github.com/tennaito/rsql-jpa instead.
Java
8
star
50

github-pr-closer

GitHub webhook handler for closing pull requests that have been merged using rebase etc.
Python
8
star
51

sloci-image

Simple script for creating single-layer OCI images.
Shell
8
star
52

dokuwiki2adoc

Converter from DokuWiki to AsciiDoc formatted text files.
Shell
8
star
53

brieflz.lua

Lua binding for BriefLZ compression library
C
7
star
54

asciidoctor-interdoc-reftext

Asciidoctor extension providing implicit (automatic) reference text (label) for inter-document cross references
Ruby
7
star
55

spring-boot-openrc

OpenRC init script for Java applications based on Spring Boot
Shell
7
star
56

muacme

A convenient wrapper for the ACMEv2 client uacme
Shell
6
star
57

rsub-client

Open and edit files from a remote machine in your local Sublime Text or TextMate 2.
Python
6
star
58

git-metafile

Store and restore files metadata (mode, owner, group) in a git repository
Rust
6
star
59

unboundid-spring

Spring Factory Beans for UnboundID LDAP SDK
Java
6
star
60

emscripten-travis-example

How to easily use Emscripten on Travis CI or any other CI
C
5
star
61

efi-mkkeys

Script to easily generate self-signed UEFI keys for Secure Boot
Shell
5
star
62

cesnet-tcs-cli

CLI client utility for CESNET TCS API
Shell
5
star
63

slava-ukrajine

Слава Україні! / Sláva Ukrajině! – grafika
4
star
64

alpkit

Rust library and CLI tool for reading Alpine Linux’s apk package format and APKBUILD
Rust
4
star
65

asciidoctor-templates-compiler

Compile templates-based Asciidoctor converter (backend) into a single Ruby file
Ruby
4
star
66

spring-http-client-cache

A very simple HTTP cache for the Spring’s RestTemplate.
4
star
67

one-context

OpenNebula contextualization scripts for Alpine Linux and Gentoo
Shell
4
star
68

uidmapshift

Shift UIDs/GIDs of directory entries recursively by some offset
Lua
4
star
69

yaml-env-tag

Custom YAML tag for referring environment variables in YAML documents
Ruby
3
star
70

mtype

An enhanced Lua type() function that looks for __type metafield
Lua
3
star
71

CSFD-parser

Parser for movie pages and search on CSFD.cz
Python
3
star
72

commons-hibernate

My collection of reusable Java classes for Hibernate.
Java
3
star
73

apk-deploy-tool

Tool for easily deploying applications or configuration packaged in APK packages via SSH
Shell
3
star
74

collectd-apk

Collectd plugin for apk-tools
C
2
star
75

tash

WIP
Shell
2
star
76

acpi-utils

ACPI utilities for use in scripts and one-liners
Shell
2
star
77

gversion.lua

Lua library for Gentoo-style versioning format
Lua
2
star
78

jabber-migrate

Tool for migration of a roster from one Jabber server to another.
Java
2
star
79

alpine-git-mirror-syncd

Lua script that listens on MQTT and synchronizes Git mirrors when notified about changes
Lua
2
star
80

spring-modular

Modularize Spring applications simply!
Java
2
star
81

spring-security-oauth-samples

Modified samples from Spring Security OAuth project
Java
2
star
82

virt-init

Provisioning scripts for Alpine Linux VMs
Shell
2
star
83

collectd-openrc

Collectd plugin for OpenRC
C
2
star
84

macos-init

Simplified cloud-init for macOS
Shell
2
star
85

com.meetfranz.Franz

Flatpak for Franz
1
star
86

prebackup

Pre/post backup scripts
Shell
1
star
87

nginx-oidc-njs

OpenID Connect and OAuth 2.0 module for NGINX written in njs.
TypeScript
1
star
88

slim-htag

Slim filter providing a heading tag with parametrized (dynamic) level (h1-h6)
Ruby
1
star
89

beuri-parser

Parser of Boolean Expressions in URI
Java
1
star
90

my-void-packages

My package templates for Void Linux
Shell
1
star
91

ts-transformer-export-default-name

TypeScript AST transformer that assigns name to anonymous functions and classes exported as default
TypeScript
1
star
92

ansible-modules

Some unofficial Ansible modules.
Python
1
star
93

roundcube-virtuser_ldap

A Roundcube plugin for LDAP based User-to-Email and Email-to-User lookup
PHP
1
star
94

sublimedsl

Simple pythonic DSL for generating Sublime Text configs.
Python
1
star
95

shaj

C
1
star
96

atom-jaxb

Custom JAXB classes for Atom Syndication Format
Java
1
star
97

maven-support

Parent POMs for my OSS projects.
1
star
98

connman-resolvconf

ConnMan integration with resolvconf(8)
Rust
1
star
99

redmine_agile

Archive of redmine_agile plugin downloaded from https://www.redmineup.com/pages/plugins/agile
Ruby
1
star
100

guacamole-dotool

A command-line Guacamole client and JS library for headless scripting (just like vncdotool, but over Guacamole)
TypeScript
1
star