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

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 {
// [...]
}

Related

custom Exceptions while working on spring boot web

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

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

Spring Boot 2.1 : Exception thrown in WebMvcConfigurer#addFormatters(...) not catched in #RestControllerAdvice

After updating from Spring Boot 2.0 to 2.1, all exceptions thrown in WebMvcConfigurer#addFormatters( FormatterRegistry registry ) are no longer catched in the #RestControllerAdvice. I use this method for additionnal converters.
Eg.:
public class ConvertersContainer {
public static class StringToStatusConverter implements Converter<String, Status> {
#Override
public Status convert( String source ) {
return Status.findStatus( source );
}
}
}
And Status is an enum.
public enum Status {
HAPPY("happy"), ANGRY("angry");
private String title;
public static Status findStatus( final String title) {
return stream( values() )
.filter( status-> status.getTitle().equals( title) )
.findFirst()
.orElseThrow( () -> new StatusNotFoundException( "...." ) );
}
}
And StatusNotFoundException extends RuntimeException {}
I registered this converter like this:
#Configuration
public class ConverterRegister implements WebMvcConfigurer {
#Override
public void addFormatters( FormatterRegistry registry ) {
registry.addConverter( new ConvertersContainer.StringToStatusConverter() );
WebMvcConfigurer.super.addFormatters( registry );
}
}
And the controllerAdvice:
#RestControllerAdvice
public class Advice {
#ExceptionHandler( StatusNotFoundException .class)
protected String handleStatusNotFoundException(StatusNotFoundException ex) { ... }
}
When I breakpoint the method Status#findStatus(...) is well executed but the exception is never catched in #RestControllerAdvice. What am I doing wrong?
Thanks a lot
It seems like Spring wrappes all exceptions thrown during the conversion's process and throws a org.springframework.beans.TypeMismatchException instead of user's custom exception.
In my mind this behavior is not normal, if an exception is thrown during the conversion, this exception should take precedence over all framework's exception. So to solve this issue, we have to extends ResponseEntityExceptionHandler and override its protected ResponseEntity<Object> handleTypeMismatch(...).
Thanks #Eric (the guy commented the question above).

In Spring how to send enum as response in controller

I have Enum Class. I Need to send a Enum class Response from the Spring controller.
I am not able understand how to sent class as Response in spring controller. Please help me for that.
You can add anything which Jackson can de-serialize in a reponse
#RestController
public class HelloController {
#RequestMapping("/monday")
public ResponseEntity<DayOfWeek> monday() {
return new ResponseEntity<DayOfWeek>(DayOfWeek.MONDAY, HttpStatus.OK);
}
#RequestMapping("/days")
public ResponseEntity<List<DayOfWeek>> days() {
return new ResponseEntity<List<DayOfWeek>>(Arrays.asList(DayOfWeek.values()), HttpStatus.OK);
}
}
You can prove this to yourself with the following test, just do the Jacskon de-serialization manually
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class HelloControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void monday() throws Exception {
String json = new ObjectMapper().writeValueAsString(DayOfWeek.MONDAY);
mvc.perform(MockMvcRequestBuilders.get("/monday").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
.andExpect(content().string(equalTo(json)));
}
#Test
public void days() throws Exception {
String json = new ObjectMapper().writeValueAsString(Arrays.asList(DayOfWeek.values()));
mvc.perform(MockMvcRequestBuilders.get("/days").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
.andExpect(content().string(equalTo(json)));
}
}
If you wanna return all enum values than try something like this:
#GetMapping("enum")
public List<MyEnum> paymentMethods() {
return Arrays.asList(MyEnum.values());
}
public enum MyEnum {
FIRST, SECOND, THIRD;
}

Resources