custom Exceptions while working on spring boot web - spring

I have a Controller which is implementing ErrorController
which handles any error that occurs in my spring project, below is the code.
#Controller
public class CustomErrorController implements ErrorController {
#RequestMapping("/error")
public void springWebErrors() {
return "springWebErrorPage"
}
}
also, I have mentioned
server.error.path=/error
but I am stuck where sometimes data might be not as per needs, so I want to give my customized messages,
are there any ideas on how to achieve it? (thanks)

As far as I understood your concern,
You want your application to handle errors/ exceptions when user sends invalid data,
I have applied same thing in my code using custom Exceptions, ControllerAdvice & Exception Handler,
please check the below code, which might be useful.
#ControllerAdvice
public class ExceptionController {
#ExceptionHandler(value = PageNotFoundException.class)
public String pageNotFoundException(PageNotFoundException exception){
return "error/404";
}
#ExceptionHandler(value = AuthFailedException.class)
public String authFailedException(AuthFailedException exception){
return "error/401";
}
#ExceptionHandler(value = ServerException.class)
public String serverException(ServerException exception){
return "error/500";
}
}
Explanation: #ControllerAdvice & #ExceptionHandler is global error controller, you can visit the documentation here
#Controller
public class CustomizedErrorController implements ErrorController {
#RequestMapping("/error")
public void handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
int statusCode = Integer.parseInt(status.toString());
if(statusCode == HttpStatus.NOT_FOUND.value()) {
throw new PageNotFoundException();
}
else if(statusCode == HttpStatus.UNAUTHORIZED.value()) {
throw new AuthFailedException();
}
else if(statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
throw new ServerException();
}
}
else{
throw new OtherException();
}
}
}
You can also throw your custom exception from your implementation or controller file.
I hope, it helps

Related

I want to return status code of 400 instead of 500 about unhandled exceptions, globally

I am trying to make 500 Internal Server Error to 400 Bad Request in every cases.
This is my package structure for exceptions.
For example, ConflictExeption looks like this.
#ResponseStatus(HttpStatus.CONFLICT)
public class ConflictException extends ApiException {
public ConflictException(String message) {
super(message);
}
public ConflictException() {
this("Conflict Exception.");
}
}
public class ApiException extends RuntimeException {
public ApiException(String message) {
super(message);
}
}
public class UserEmailInUseException extends ConflictException{
public static final String MESSAGE = "Desired email is already in use.";
public UserEmailInUseException() {
super(MESSAGE);
}
}
Below is my simple service code.
#Service
public class UsersService {
#Transactional
public UserInfoResponseDto signUp(UserSignupRequestDto requestDto) {
if (usersRepository.findByEmail(requestDto.getEmail()).isPresent()) throw new
UserEmailInUseException();
return new UserInfoResponseDto(usersRepository.save(requestDto.toEntity()));
}
}
UserEmailInUseException extends ConflictException.
In my case, 500 error occurs when some violations are made while making transaction with MariaDB.
Simply put, I just want to return status code of 400 instead of 500, where exception is not handled.
Thanks in advance!
Also, I've tried this example below, and it seems to send 500..
Link
Have you tried:
#RestControllerAdvice
public class RestErrorHandler {
#ExceptionHandler
#ResponseStatus(BAD_REQUEST)
Exception handleUnhandledException(Exception e) {
return e;
}
}
First of all you are mapping it to CONFLICT instead of BAD_REQUEST. Have you tried the following annotation setting?
#ResponseStatus(HttpStatus.BAD_REQUEST)
If you don't want to change existing ConflictException try to introduce a new base exception as follows:
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends ApiException {
// [...]
}

#ControllerAdvice even by setting the highest precedense for RestControllers not working as it should

I am using SpringBoot 5.
I want to catch all exception thrown from RestController and display customize message format.
I have simplified the situation like below:
The RestController
#RestController
#RequestMapping("/test")
public class TestRestController {
#Autowired
private TestService testService;
#GetMapping("/{id}")
public ResponseEntity<?> findById(#PathVariable int id) {
Test test = testService.find(id);
if(department!=null){
throw CustomException();
}
return new ResponseEntity<>(test, HttpStatus.OK);
}
}
The ControllerAdvice Exception handler:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice(annotations = RestController.class)
public class RestExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RestExceptionHandler.class);
#ExceptionHandler(value= {CustomException.class})
public ResponseEntity<ErrorDetail> handleCustomException(CustomException exception,
HttpServletRequest request) {
ErrorDetail errorDetail = new ErrorDetail();
errorDetail.setTimeStamp(Instant.now().getEpochSecond());
errorDetail.setStatus(HttpStatus.NOT_FOUND.value());
errorDetail.setTitle("Resource Not Found");
errorDetail.setDetail(exception.getMessage());
errorDetail.setDeveloperMessage(exception.getClass().getName());
return new ResponseEntity<>(errorDetail, null, HttpStatus.NOT_FOUND);
}
}
The problem it is that RestExceptionHandler is not working, it is not catching the exception and returning the modified error message format. It seem my RestExceptionControllerClass is not overriding the GlobalExceptionHandler. I don't know why this is happening because I have marked the RestExceptionHandler with the highest precedense. I will appriciate any guidence to debug this problem.
#ControllerAdvice
public class RestExceptionHandler {
#ResponseStatus(HttpStatus.NOT_FOUND)
#ResponseBody
#ExceptionHandler(CustomException.class)
public ErrorDetail handleCustomException(CustomException exception) {
ErrorDetail errorDetail = new ErrorDetail();
errorDetail.setTimeStamp(Instant.now().getEpochSecond());
errorDetail.setTitle("Resource Not Found");
errorDetail.setDetail(exception.getMessage());
errorDetail.setDeveloperMessage(exception.getClass().getName());
return errorDetail;
}
}
Refer this link to know more about exception handling for REST API
https://www.baeldung.com/exception-handling-for-rest-with-spring
Change your RestExceptionHandler class like below
#RestControllerAdvice
public class RestExceptionHandler {
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(CustomException.class)
public ResponseEntity<ErrorDetail> handleCustomException(CustomException exception) {
ErrorDetail errorDetail = new ErrorDetail();
errorDetail.setTimeStamp(Instant.now().getEpochSecond());
errorDetail.setTitle("Resource Not Found");
errorDetail.setDetail(exception.getMessage());
errorDetail.setDeveloperMessage(exception.getClass().getName());
return new ResponseEntity<>(errorDetail, null, HttpStatus.NOT_FOUND);
}
}
And you also need to extends RuntimeException in your CustomException class
The problem was that another exception was thrown before my CustomException. In the service call , there was part of code that threw an exception that i did not expect. So my RestExceptionHandler couldn't catch the exception because it didn't have a method to handle that exception and so the GlobalExceptionHandler was handling that exception. After fixing the code and made sure that the CustomExeption was thrown everything worked as it should. The RestExceptionHandler handled exception and printed the custom message.

Is there a way in spring boot to manually invoke the Exception Advice?

I have a scenario where is an already existing controller and the service throws exceptions which are handled via the #RestControllerAdvice.
Now i have a new class which i have introduced which invokes methods from the above service class in a batch mode. In my class i have to capture the exceptions or successes bundle them up and return. For any exceptions that occur i need to report the HTTP Status and the error message.
Could you let me know if there is any way this can be achieved?
You can create your own Exception class.
public class MyException extends Exception {
private int errorCode;
private String errorMessage;
public MyException(int errorCode, String errorMessage) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
}
and you can create new MyException when occurring any exception and throw it. Then you get this exception in the #RestControllerAdvice class.
#RestControllerAdvice
public class ExceptionAdvice {
private ErrorCodeMapper errorCodeMapper;
#Autowired
public ExceptionAdvice(ErrorCodeMapper errorCodeMapper) {
this.errorCodeMapper = errorCodeMapper;
}
#ExceptionHandler(value = MyException.class)
public ResponseEntity handleGenericNotFoundException(MyException e) {
return new ResponseEntity(errorCodeMapper.getStatusCode(e.getErrorCode()));
}
}
and mapper class like below:
#Service
public class ErrorCodeMapper {
public static Map<Integer,HttpStatus> errorCodeMap = new HashMap<>();
public ErrorCodeMapper(){
errorCodeMap.put(100, HttpStatus.BAD_REQUEST);
errorCodeMap.put(101,HttpStatus.OK);
errorCodeMap.put(102,HttpStatus.BAD_REQUEST);
errorCodeMap.put(103,HttpStatus.BAD_REQUEST);
}
HttpStatus getStatusCode(int errorCode){
return errorCodeMap.get(errorCode);
}
}
You can more details to MyException and add the error message to the ResponseEntity.

Better way to handle exception is spring-boot

I have some Ten API which talks to redis for storing the data.
Currently it is throwing nested exception, so I have done like below to handle the nested exception.
#Override
public boolean delMyStatus(String key) {
try{
return redisTemplate.delete(key);
}
catch (Exception e){
if(e.getCause() != null && e.getCause().getCause() instanceof RedisException) {
RedisException ex = (RedisException)e.getCause().getCause();
log.error("RedisException " + ex.getMessage());
/* Do some action*/
} else {
throw new IllegalStateException("...");
}
}
return false;
}
But I dont want to do this for all the APIS of redis dao, Is there any better way to handle exception.
You can use #RestControllerAdvice. Make a custom exception class CustomRedisException throw CustomRedisException Exception from every controller and handle this in separate class annotated with #RestControllerAdvice.
#Override
public boolean delMyStatus(String key) {
try{
return redisTemplate.delete(key);
}
catch (Exception e){
if(e.getCause() != null && e.getCause().getCause() instanceof RedisException) { RedisException ex = (RedisException)e.getCause().getCause();
throw new CustomRedisException(ex);
} else {
throw new IllegalStateException("...");
}
}
return false;
}
Make GlobalExceptionHandler like below.
#RestControllerAdvice(basePackages = "your base package here", basePackageClasses = RepositoryRestExceptionHandler.class)
public class GlobalRestExceptionHandler {
#ExceptionHandler
public ResponseEntity<ErrorResponse> handleCustomException(final CustomRedisExceptionex) {
// code for exception handling here.
return new ResponseEntity<>(
new ErrorResponse(HttpStatus.PRECONDITION_FAILED.value(), ex.getMessage()),
HttpStatus.PRECONDITION_FAILED);
}
}
You can achieve it with aspects and #AfterThrowing annotation.
First make sure you allowed Spring to use aspects using #EnableAspectJAutoProxy annotation on any of your configuration classes.
Then define an #Aspect class with method annotated with #AfterThrowing like this:
#Aspect
public class GenericExceptionHandler {
// you can use more specific path here
#AfterThrowing ("execution(* *.*(..))", throwing = "ex")
public void handleException(Exception ex) throws Exception {
// handle the exception here
}
}

Spring MVC - RestTemplate launch exception when http 404 happens

I have a rest service which send an 404 error when the resources is not found.
Here the source of my controller and the exception which send Http 404.
#Controller
#RequestMapping("/site")
public class SiteController
{
#Autowired
private IStoreManager storeManager;
#RequestMapping(value = "/stores/{pkStore}", method = RequestMethod.GET, produces = "application/json")
#ResponseBody
public StoreDto getStoreByPk(#PathVariable long pkStore) {
Store s = storeManager.getStore(pkStore);
if (null == s) {
throw new ResourceNotFoundException("no store with pkStore : " + pkStore);
}
return StoreDto.entityToDto(s);
}
}
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException
{
private static final long serialVersionUID = -6252766749487342137L;
public ResourceNotFoundException(String message) {
super(message);
}
}
When i try to call it with RestTemplate with this code :
ResponseEntity<StoreDto> r = restTemplate.getForEntity(url, StoreDto.class, m);
System.out.println(r.getStatusCode());
System.out.println(r.getBody());
I receive this exception :
org.springframework.web.client.RestTemplate handleResponseError
ATTENTION: GET request for "http://........./stores/99" resulted in 404 (Introuvable); invoking error handler
org.springframework.web.client.HttpClientErrorException: 404 Introuvable
I was thinking I can explore my responseEntity Object and do some things with the statusCode. But exception is launch and my app go down.
Is there a specific configuration for restTemplate to not send exception but populate my ResponseEntity.
As far as I'm aware, you can't get an actual ResponseEntity, but the status code and body (if any) can be obtained from the exception:
try {
ResponseEntity<StoreDto> r = restTemplate.getForEntity(url, StoreDto.class, m);
}
catch (final HttpClientErrorException e) {
System.out.println(e.getStatusCode());
System.out.println(e.getResponseBodyAsString());
}
RESTTemplate is quite deficient in this area IMO. There's a good blog post here about how you could possibly extract the response body when you've received an error:
http://springinpractice.com/2013/10/07/handling-json-error-object-responses-with-springs-resttemplate
As of today there is an outstanding JIRA request that the template provides the possibility to extract the response body:
https://jira.spring.io/browse/SPR-10961
The trouble with Squatting Bear's answer is that you would have to interrogate the status code inside the catch block eg if you're only wanting to deal with 404's
Here's how I got around this on my last project. There may be better ways, and my solution doesn't extract the ResponseBody at all.
public class ClientErrorHandler implements ResponseErrorHandler
{
#Override
public void handleError(ClientHttpResponse response) throws IOException
{
if (response.getStatusCode() == HttpStatus.NOT_FOUND)
{
throw new ResourceNotFoundException();
}
// handle other possibilities, then use the catch all...
throw new UnexpectedHttpException(response.getStatusCode());
}
#Override
public boolean hasError(ClientHttpResponse response) throws IOException
{
return response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR
|| response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR;
}
The ResourceNotFoundException and UnexpectedHttpException are my own unchecked exceptions.
The when creating the rest template:
RestTemplate template = new RestTemplate();
template.setErrorHandler(new ClientErrorHandler());
Now we get the slightly neater construct when making a request:
try
{
HttpEntity response = template.exchange("http://localhost:8080/mywebapp/customer/100029",
HttpMethod.GET, requestEntity, String.class);
System.out.println(response.getBody());
}
catch (ResourceNotFoundException e)
{
System.out.println("Customer not found");
}
Since it's 2018 and I hope that when people say "Spring" they actually mean "Spring Boot" at least, I wanted to expand the given answers with a less dust-covered approach.
Everything mentioned in the previous answers is correct - you need to use a custom ResponseErrorHandler.
Now, in Spring Boot world the way to configure it is a bit simpler than before.
There is a convenient class called RestTemplateBuilder. If you read the very first line of its java doc it says:
Builder that can be used to configure and create a RestTemplate.
Provides convenience methods to register converters, error handlers
and UriTemplateHandlers.
It actually has a method just for that:
new RestTemplateBuilder().errorHandler(new DefaultResponseErrorHandler()).build();
On top of that, Spring guys realized the drawbacks of a conventional RestTemplate long time ago, and how it can be especially painful in tests. They created a convenient class, TestRestTemplate, which serves as a wrapper around RestTemplate and set its errorHandler to an empty implementation:
private static class NoOpResponseErrorHandler extends
DefaultResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse response) throws IOException {
}
}
You can create your own RestTemplate wrapper which does not throw exceptions, but returns a response with the received status code. (You could also return the body, but that would stop being type-safe, so in the code below the body remains simply null.)
/**
* A Rest Template that doesn't throw exceptions if a method returns something other than 2xx
*/
public class GracefulRestTemplate extends RestTemplate {
private final RestTemplate restTemplate;
public GracefulRestTemplate(RestTemplate restTemplate) {
super(restTemplate.getMessageConverters());
this.restTemplate = restTemplate;
}
#Override
public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException {
return withExceptionHandling(() -> restTemplate.getForEntity(url, responseType));
}
#Override
public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException {
return withExceptionHandling(() -> restTemplate.postForEntity(url, request, responseType));
}
private <T> ResponseEntity<T> withExceptionHandling(Supplier<ResponseEntity<T>> action) {
try {
return action.get();
} catch (HttpClientErrorException ex) {
return new ResponseEntity<>(ex.getStatusCode());
}
}
}
Recently had a usecase for this. My solution:
public class MyErrorHandler implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
return hasError(clientHttpResponse.getStatusCode());
}
#Override
public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
HttpStatus statusCode = clientHttpResponse.getStatusCode();
MediaType contentType = clientHttpResponse
.getHeaders()
.getContentType();
Charset charset = contentType != null ? contentType.getCharset() : null;
byte[] body = FileCopyUtils.copyToByteArray(clientHttpResponse.getBody());
switch (statusCode.series()) {
case CLIENT_ERROR:
throw new HttpClientErrorException(statusCode, clientHttpResponse.getStatusText(), body, charset);
case SERVER_ERROR:
throw new HttpServerErrorException(statusCode, clientHttpResponse.getStatusText(), body, charset);
default:
throw new RestClientException("Unknown status code [" + statusCode + "]");
}
}
private boolean hasError(HttpStatus statusCode) {
return (statusCode.series() == HttpStatus.Series.CLIENT_ERROR ||
statusCode.series() == HttpStatus.Series.SERVER_ERROR);
}
There is no such class implementing ResponseErrorHandler in Spring framework, so I just declared a bean:
#Bean
public RestTemplate getRestTemplate() {
return new RestTemplateBuilder()
.errorHandler(new DefaultResponseErrorHandler() {
#Override
public void handleError(ClientHttpResponse response) throws IOException {
//do nothing
}
})
.build();
}
The best way to make a RestTemplate to work with 4XX/5XX errors without throwing exceptions I found is to create your own service, which uses RestTemplate :
public ResponseEntity<?> makeCall(CallData callData) {
logger.debug("[makeCall][url] " + callData.getUrl());
logger.debug("[makeCall][httpMethod] " + callData.getHttpMethod());
logger.debug("[makeCall][httpEntity] " + callData.getHttpEntity());
logger.debug("[makeCall][class] " + callData.getClazz());
logger.debug("[makeCall][params] " + callData.getQueryParams());
ResponseEntity<?> result;
try {
result = restTemplate.exchange(callData.getUrl(), callData.getHttpMethod(), callData.getHttpEntity(),
callData.getClazz(), callData.getQueryParams());
} catch (RestClientResponseException e) {
result = new ResponseEntity<String>(e.getResponseBodyAsString(), e.getResponseHeaders(), e.getRawStatusCode());
}
return result;
}
And in case of exception, simply catch it and create your own ResponseEntity.
This will allow you to work with the ResponseEntity object as excepted.

Resources