[Spring Boot2] Common exception handler
When we develop API, how to handle exception is very important for us to locate error, find out exact error code.
Recently I extracted exceptionHandler to a common framework, and deploy it into our internal maven repository. If anyone wanna use it, just add maven dependency, and it will works automatically.
1.Add internal repository and dependency in your pom.xml
<repositories>
<repository>
<id>nexus-central</id>
<url>https://nexus.tool.xxx.internal/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>
<dependency>
<groupId>com.xxx</groupId>
<artifactId>xxx-framework-web</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
2.Add package scan annotation, the package prefix must be the same as framework’s package
@ComponentScan(basePackages = "com.xxx.xxx.api")
Then it will intercept all the exception and print error logs and respond to the client.
Error Logs:
2018-04-19 07:02:24,785 - [http-nio-8080-exec-2] - [ERROR] - [xxx.xxx.xxx.api.framework.aop.ApiExceptionHandler.internalErrorHandler(line:26)] - org.springframework.jdbc.BadSqlGrammarException:
### SQL: SELECT id, experiment_id, start_at, model, created_at, created_by, updated_at, updated_by FROM XXX WHERE experiment_id = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'xxx_xxx.XXX' doesn't exist
; bad SQL grammar []; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'xxx_xxx.XXX' doesn't exist
at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:235)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73)
...
...
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'xxx_xxx.XXX' doesn't exist
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
...
...
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)
... 95 more
Client Response:
{
"timestamp": "2018-05-03T07:02:24Z",
"status": 500,
"error": "Internal Server Error",
"message": "n### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'xxx_xxx.xxx' doesn't existn### The error may exist in file [/Users/xxx/java/xxx-api-xxx/target/classes/com/xxx/xxx/api/xxx/mapper/xxxMapper.xml]n### The error may involve defaultParameterMapn### The error occurred while setting parametersn### SQL: SELECT id, department_id, start_at, created_at, created_by, updated_at, updated_by FROM xxx WHERE department_id = ?n### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'xxx_xxx.xxx' doesn't existn; bad SQL grammar []; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'xxx_xxx.xxx' doesn't exist",
"path": "/v1/employees",
"method": "GET"
}
ApiExceptionHandler:
@RestControllerAdvice
@Slf4j
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public ApiResponseEntity internalErrorHandler(Exception exception, ServletWebRequest request) {
log.error("{}", ExceptionUtils.getStackTrace(exception));
return new ApiResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(),
exception.getMessage(), request);
}
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(code = HttpStatus.NOT_FOUND)
public ApiResponseEntity notFoundHandler(Exception exception, ServletWebRequest request) {
log.warn("{}", exception.getMessage());
return new ApiResponseEntity(HttpStatus.NOT_FOUND.value(), HttpStatus.NOT_FOUND.getReasonPhrase(),
exception.getMessage(), request);
}
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
public ApiResponseEntity illegalArgumentException(Exception exception, ServletWebRequest request) {
log.warn("{}", exception.getMessage());
return new ApiResponseEntity(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase(),
exception.getMessage(), request);
}
@Override
protected ResponseEntity<Object> handleExceptionInternal(
Exception exception, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
log.warn("{}", exception.getMessage());
if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, exception, WebRequest.SCOPE_REQUEST);
}
ServletWebRequest servletWebRequest = (ServletWebRequest) request;
ApiResponseEntity apiResponseEntity = new ApiResponseEntity(status.value(), status.getReasonPhrase(), exception.getMessage(), servletWebRequest);
return new ResponseEntity<>(apiResponseEntity, headers, status);
}
}
Reply