{"id":14982985,"url":"https://github.com/jirutka/spring-rest-exception-handler","last_synced_at":"2025-04-04T09:10:04.131Z","repository":{"id":15641412,"uuid":"18378441","full_name":"jirutka/spring-rest-exception-handler","owner":"jirutka","description":"A convenient Spring MVC exception handler for RESTful APIs.","archived":false,"fork":false,"pushed_at":"2024-06-02T08:57:04.000Z","size":178,"stargazers_count":357,"open_issues_count":15,"forks_count":129,"subscribers_count":36,"default_branch":"master","last_synced_at":"2025-03-28T08:09:50.481Z","etag":null,"topics":["exception-handler","groovy","java","rest","spring-mvc","springframework"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jirutka.png","metadata":{"files":{"readme":"README.adoc","changelog":"CHANGELOG.adoc","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-04-02T19:23:37.000Z","updated_at":"2025-01-31T10:41:43.000Z","dependencies_parsed_at":"2024-12-25T11:12:33.603Z","dependency_job_id":"a7a32174-7f02-48c5-af09-43e2c274aab3","html_url":"https://github.com/jirutka/spring-rest-exception-handler","commit_stats":{"total_commits":100,"total_committers":6,"mean_commits":"16.666666666666668","dds":"0.050000000000000044","last_synced_commit":"401cb9e51b1b4e14c9350b06f8e0029a34ce6853"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jirutka%2Fspring-rest-exception-handler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jirutka%2Fspring-rest-exception-handler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jirutka%2Fspring-rest-exception-handler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jirutka%2Fspring-rest-exception-handler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jirutka","download_url":"https://codeload.github.com/jirutka/spring-rest-exception-handler/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247149505,"owners_count":20891954,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["exception-handler","groovy","java","rest","spring-mvc","springframework"],"created_at":"2024-09-24T14:06:32.331Z","updated_at":"2025-04-04T09:10:04.104Z","avatar_url":"https://github.com/jirutka.png","language":"Java","readme":"= Spring REST Exception handler\n:source-language: java\n// Project meta\n:name: spring-rest-exception-handler\n:version: 1.2.0\n:group-id: cz.jirutka.spring\n:artifact-id: {name}\n:gh-name: jirutka/{name}\n:gh-branch: master\n:codacy-id: ca5dbac87d564725b6640a67b2b7ea35\n// URIs\n:src-base: link:src/main/java/cz/jirutka/spring/exhandler\n:spring-jdoc-uri: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework\n\nifdef::env-github[]\nimage:https://travis-ci.org/{gh-name}.svg?branch={gh-branch}[\"Build Status\", link=\"https://travis-ci.org/{gh-name}\"]\nimage:https://coveralls.io/repos/github/{gh-name}/badge.svg?branch={gh-branch}[Coverage Status, link=\"https://coveralls.io/github/{gh-name}\"]\nimage:https://api.codacy.com/project/badge/grade/{codacy-id}[\"Codacy code quality\", link=\"https://www.codacy.com/app/{gh-name}\"]\nimage:https://maven-badges.herokuapp.com/maven-central/{group-id}/{artifact-id}/badge.svg[Maven Central, link=\"https://maven-badges.herokuapp.com/maven-central/{group-id}/{artifact-id}\"]\nendif::env-github[]\n\n\nThe 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.\nIt’s very easy to handle custom exceptions, customize error responses and even localize them.\nAlso solves some pitfalls footnote:[Nothing terrible, Spring MVC is still a far better then JAX-RS for RESTful APIs! ;)] in Spring MVC with a content negotiation when producing error responses.\n\n\n== Error message\n\nError messages generated by `ErrorMessageRestExceptionHandler` follows the http://tools.ietf.org/html/draft-nottingham-http-problem-06[Problem Details for HTTP APIs] specification.\n\nFor example, the following error message describes a validation exception.\n\n*In JSON format:*\n\n[source, json]\n----\n{\n    \"type\": \"http://example.org/errors/validation-failed\",\n    \"title\": \"Validation Failed\",\n    \"status\": 422,\n    \"detail\": \"The content you've send contains 2 validation errors.\",\n    \"errors\": [{\n        \"field\": \"title\",\n        \"message\": \"must not be empty\"\n    }, {\n        \"field\": \"quantity\",\n        \"rejected\": -5,\n        \"message\": \"must be greater than zero\"\n    }]\n}\n----\n\n*… or in XML:*\n\n[source,xml]\n----\n\u003cproblem\u003e\n    \u003ctype\u003ehttp://example.org/errors/validation-failed\u003c/type\u003e\n    \u003ctitle\u003eValidation Failed\u003c/title\u003e\n    \u003cstatus\u003e422\u003c/status\u003e\n    \u003cdetail\u003eThe content you've send contains 2 validation errors.\u003c/detail\u003e\n    \u003cerrors\u003e\n        \u003cerror\u003e\n            \u003cfield\u003etitle\u003c/field\u003e\n            \u003cmessage\u003emust not be empty\u003c/message\u003e\n        \u003c/error\u003e\n        \u003cerror\u003e\n            \u003cfield\u003equantity\u003c/field\u003e\n            \u003crejected\u003e-5\u003c/rejected\u003e\n            \u003cmessage\u003emust be greater than zero\u003c/message\u003e\n        \u003c/error\u003e\n    \u003c/errors\u003e\n\u003c/problem\u003e\n----\n\n\n== How does it work?\n\n=== RestHandlerExceptionResolver\n\nThe core class of this library that resolves all exceptions is {src-base}/RestHandlerExceptionResolver.java[RestHandlerExceptionResolver].\nIt holds a registry of `RestExceptionHandlers`.\n\nWhen your controller throws an exception, the `RestHandlerExceptionResolver` will:\n\n. 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.\n. Find the best matching media type to produce (using {spring-jdoc-uri}/web/accept/ContentNegotiationManager.html[ContentNegotiationManager], utilises _Accept_ header by default). When the requested media type is not supported, then fallback to the configured default media type.\n. Write the response.\n\n\n=== RestExceptionHandler\n\nImplementations of the {src-base}/handlers/RestExceptionHandler.java[RestExceptionHandler] interface are responsible for converting the exception into Spring’s {spring-jdoc-uri}/http/ResponseEntity.html[ResponseEntity] instance that contains a body, headers and a HTTP status code.\n\nThe main implementation is {src-base}/handlers/ErrorMessageRestExceptionHandler.java[ErrorMessageRestExceptionHandler] that produces the `ErrorMessage` body (see above for example).\nAll the attributes (besides status) are loaded from a properties file (see the section \u003c\u003cLocalizable error messages\u003e\u003e).\nThis class also logs the exception (see the \u003c\u003cException logging\u003e\u003e section).\n\n\n== Configuration\n\n=== Java-based configuration\n\n[source]\n----\n@EnableWebMvc\n@Configuration\npublic class RestContextConfig extends WebMvcConfigurerAdapter {\n\n    @Override\n    public void configureHandlerExceptionResolvers(List\u003cHandlerExceptionResolver\u003e resolvers) {\n        resolvers.add( exceptionHandlerExceptionResolver() ); // resolves @ExceptionHandler\n        resolvers.add( restExceptionResolver() );\n    }\n\n    @Bean\n    public RestHandlerExceptionResolver restExceptionResolver() {\n        return RestHandlerExceptionResolver.builder()\n                .messageSource( httpErrorMessageSource() )\n                .defaultContentType(MediaType.APPLICATION_JSON)\n                .addErrorMessageHandler(EmptyResultDataAccessException.class, HttpStatus.NOT_FOUND)\n                .addHandler(MyException.class, new MyExceptionHandler())\n                .build();\n    }\n\n    @Bean\n    public MessageSource httpErrorMessageSource() {\n        ReloadableResourceBundleMessageSource m = new ReloadableResourceBundleMessageSource();\n        m.setBasename(\"classpath:/org/example/messages\");\n        m.setDefaultEncoding(\"UTF-8\");\n        return m;\n    }\n\n    @Bean\n    public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver() {\n        ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();\n        resolver.setMessageConverters(HttpMessageConverterUtils.getDefaultHttpMessageConverters());\n        return resolver;\n    }\n}\n----\n\n\n=== XML-based configuration\n\n[source, xml]\n----\n\u003cbean id=\"compositeExceptionResolver\"\n      class=\"org.springframework.web.servlet.handler.HandlerExceptionResolverComposite\"\u003e\n    \u003cproperty name=\"order\" value=\"0\" /\u003e\n    \u003cproperty name=\"exceptionResolvers\"\u003e\n        \u003clist\u003e\n            \u003cref bean=\"exceptionHandlerExceptionResolver\" /\u003e\n            \u003cref bean=\"restExceptionResolver\" /\u003e\n        \u003c/list\u003e\n    \u003c/property\u003e\n\u003c/bean\u003e\n\n\u003cbean id=\"restExceptionResolver\"\n      class=\"cz.jirutka.spring.exhandler.RestHandlerExceptionResolverFactoryBean\"\u003e\n    \u003cproperty name=\"messageSource\" ref=\"httpErrorMessageSource\" /\u003e\n    \u003cproperty name=\"defaultContentType\" value=\"application/json\" /\u003e\n    \u003cproperty name=\"exceptionHandlers\"\u003e\n        \u003cmap\u003e\n            \u003centry key=\"org.springframework.dao.EmptyResultDataAccessException\" value=\"404\" /\u003e\n            \u003centry key=\"org.example.MyException\"\u003e\n                \u003cbean class=\"org.example.MyExceptionHandler\" /\u003e\n            \u003c/entry\u003e\n        \u003c/map\u003e\n    \u003c/property\u003e\n\u003c/bean\u003e\n\n\u003cbean id=\"exceptionHandlerExceptionResolver\"\n      class=\"org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver\" /\u003e\n\n\u003cbean id=\"httpErrorMessageSource\"\n      class=\"org.springframework.context.support.ReloadableResourceBundleMessageSource\"\n      p:basename=\"classpath:/org/example/errorMessages\"\n      p:defaultEncoding=\"UTF-8\" /\u003e\n----\n\n\n=== Another resolvers\n\nThe {spring-jdoc-uri}/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.html[ExceptionHandlerExceptionResolver] is used to resolve exceptions through {spring-jdoc-uri}/web/bind/annotation/ExceptionHandler.html[@ExceptionHandler] methods.\nIt must be registered _before_ the RestHandlerExceptionResolver.\nIf you don’t have any `@ExceptionHandler` methods, then you can omit the `exceptionHandlerExceptionResolver` bean declaration.\n\n\n=== Default handlers\n\nBuilder and FactoryBean registers a set of the default handlers by default.\nThis can be disabled by setting `withDefaultHandlers` to false.\n\n\n=== Localizable error messages\n\nMessage values are read from a _properties_ file through the provided {spring-jdoc-uri}/context/MessageSource.html[MessageSource], so it can be simply customized and localized.\nLibrary contains a default link:src/main/resources/cz/jirutka/spring/exhandler/messages.properties[messages.properties] file that is implicitly set as a parent (i.e. fallback) of the provided message source.\nThis can be disabled by setting `withDefaultMessageSource` to false (on a builder or factory bean).\n\nThe 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).\n\nValue is a message template that may contain https://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html[SpEL] expressions delimited by `#{` and `}`.\nInside an expression, you can access the exception being handled and the current request (instance of http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html[HttpServletRequest]) under the `ex`, resp. `req` variables.\n\n*For example:*\n\n[source, properties]\n----\norg.springframework.web.HttpMediaTypeNotAcceptableException.type=http://httpstatus.es/406\norg.springframework.web.HttpMediaTypeNotAcceptableException.title=Not Acceptable\norg.springframework.web.HttpMediaTypeNotAcceptableException.detail=\\\n    This resource provides #{ex.supportedMediaTypes}, but you've requested #{req.getHeader('Accept')}.\n----\n\n\n=== Exception logging\n\nExceptions 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.\nThe logger name is `cz.jirutka.spring.exhandler.handlers.RestExceptionHandler` and a Marker is set to the exception’s full qualified name.\n\n\n=== Why is 404 bypassing exception handler?\n\nWhen the {spring-jdoc-uri}/web/servlet/DispatcherServlet.html[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 http://stackoverflow.com/a/22751886/2217862[on StackOverflow]).\nThis behaviour can be changed, *since Spring 4.0.0*, using `throwExceptionIfNoHandlerFound` init parameter.\nYou should set this to true for a consistent error responses.\n\n*When using WebApplicationInitializer:*\n\n[source]\n----\npublic class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {\n\n    protected void customizeRegistration(ServletRegistration.Dynamic reg) {\n        reg.setInitParameter(\"throwExceptionIfNoHandlerFound\", \"true\");\n    }\n    ...\n}\n----\n\n*…or classic web.xml:*\n\n[source, xml]\n----\n\u003cservlet\u003e\n    \u003cservlet-name\u003erest-dispatcher\u003c/servlet-name\u003e\n    \u003cservlet-class\u003eorg.springframework.web.servlet.DispatcherServlet\u003c/servlet-class\u003e\n    \u003cinit-param\u003e\n        \u003cparam-name\u003ethrowExceptionIfNoHandlerFound\u003c/param-name\u003e\n        \u003cparam-value\u003etrue\u003c/param-value\u003e\n    \u003c/init-param\u003e\n    ...\n\u003c/servlet\u003e\n----\n\n\n== How to get it?\n\nReleased versions are available in jCenter and the Central Repository.\nJust add this artifact to your project:\n\n._Maven_\n[source, xml, subs=\"verbatim, attributes\"]\n----\n\u003cdependency\u003e\n    \u003cgroupId\u003e{group-id}\u003c/groupId\u003e\n    \u003cartifactId\u003e{artifact-id}\u003c/artifactId\u003e\n    \u003cversion\u003e{version}\u003c/version\u003e\n\u003c/dependency\u003e\n----\n\n._Gradle_\n[source, groovy, subs=\"verbatim, attributes\"]\ncompile '{group-id}:{artifact-id}:{version}'\n\nHowever if you want to use the last snapshot version, you have to add the JFrog OSS repository:\n\n._Maven_\n[source, xml]\n----\n\u003crepository\u003e\n    \u003cid\u003ejfrog-oss-snapshot-local\u003c/id\u003e\n    \u003cname\u003eJFrog OSS repository for snapshots\u003c/name\u003e\n    \u003curl\u003ehttps://oss.jfrog.org/oss-snapshot-local\u003c/url\u003e\n    \u003csnapshots\u003e\n        \u003cenabled\u003etrue\u003c/enabled\u003e\n    \u003c/snapshots\u003e\n\u003c/repository\u003e\n----\n\n._Gradle_\n[source, groovy]\n----\nrepositories {\n  maven {\n    url 'https://oss.jfrog.org/oss-snapshot-local'\n  }\n}\n----\n\n\n== Requirements\n\n* Spring 3.2.0.RELEASE and newer is supported, but 4.× is highly recommended.\n* Jackson 1.× and 2.× are both supported and optional.\n\n\n== References\n\n* http://www.jayway.com/2012/09/23/improve-your-spring-rest-api-part-ii[Improve Your Spring REST API by M. Severson]\n* https://stormpath.com/blog/spring-mvc-rest-exception-handling-best-practices-part-1/[Spring MVC REST Exception Handling Best Practices by L. Hazlewood]\n* http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc[Exception Handling in Spring MVC by P. Chapman]\n* http://tools.ietf.org/html/draft-nottingham-http-problem-06[IETF draft Problem Details for HTTP APIs by M. Nottingham]\n\n\n== License\n\nThis project is licensed under http://www.apache.org/licenses/LICENSE-2.0.html[Apache License 2.0].\n","funding_links":[],"categories":["REST错误处理"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjirutka%2Fspring-rest-exception-handler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjirutka%2Fspring-rest-exception-handler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjirutka%2Fspring-rest-exception-handler/lists"}