I have added customexception class to handle my api class.
Exception class
#SuppressWarnings({ "unchecked", "rawtypes" })
#ControllerAdvice
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
ErrorMessage error = new ErrorMessage("Server Error", details);
return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(RecordNotFoundException.class)
public final ResponseEntity<Object> handleConfigNotFoundException(RecordNotFoundException ex, WebRequest request) {
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
ErrorMessage error = new ErrorMessage("Record Not Found", details);
return new ResponseEntity(error, HttpStatus.NOT_FOUND);
}
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
List<String> details = new ArrayList<>();
for (ObjectError error : ex.getBindingResult().getAllErrors()) {
details.add(error.getDefaultMessage());
}
ErrorMessage error = new ErrorMessage("Validation Failed", details);
return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
}
}
API method
#RequestMapping(value = "/config", params = { "appCode", "appVersion" }, method = RequestMethod.GET)
public ResponseEntity<ConfigResponse> getConfig(#RequestParam(value = "appCode", required = true) String appCode,
#RequestParam(value = "appVersion", required = true) String appVersion) {
List<AppConfig> result = configRepository.findByCodeAndVersion(appCode, appVersion);
if (result.isEmpty()) {
throw new RecordNotFoundException(appCode + " or " + appVersion + "does not exist");
}
AppConfig config = new AppConfig();
return new ResponseEntity<ConfigResponse>(config.convertToResponse(result), HttpStatus.OK);
}
Record Not found
#ResponseStatus(HttpStatus.NOT_FOUND)
public class RecordNotFoundException extends RuntimeException {
public RecordNotFoundException(String exception) {
super(exception);
}
}
I am checking for the record not found but it still gives me the spring exception and not my custom exception.
Related
I have a problem to return the ErrorDTO from Generic Exception.
When I send a request to http://localhost:8080/v1/locations with this response body shown below, I get this issue underneath GlobalExceptionHandler.
{
"code" : "DELHI_IN_QWERTYUI", -> IT thows an error because of its length (length is 12)
"city_name" : "New Delhi",
"region_name" : "Delhi",
"country_name" : "India",
"country_code" : "IN",
"enabled" : true
}
Here is the method defined in controller shown below?
#PostMapping
public ResponseEntity<Location> addLocation(#RequestBody #Valid Location location) {
Location addedLocation = service.add(location);
URI uri = URI.create("/v1/locations/" + addedLocation.getCode());
return ResponseEntity.created(uri).body(addedLocation);
}
How can I fix the issue?
Here is the ErrorDTO below.
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class ErrorDTO {
private LocalDateTime timestamp;
private int status;
private String path;
private HttpStatus httpStatus;
List<String> errorDetails;
}
Here is the GlobalExceptionError shown below
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// handleMissingServletRequestParameter : triggers when there are missing parameters
#Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
List<String> details = new ArrayList<String>();
StringBuilder builder = new StringBuilder();
builder.append(ex.getMessage() + " is not supported");
details.add(builder.toString());
HttpStatus httpStatus = HttpStatus.valueOf(status.value());
ErrorDTO error = new ErrorDTO.ErrorDTOBuilder()
.timestamp(LocalDateTime.now())
.status(status.value())
.errorDetails(details)
.path(request.getContextPath())
.httpStatus(httpStatus)
.build();
LOGGER.error("GlobalExceptionHandler | handleHttpRequestMethodNotSupported", error);
return ResponseEntity.status(status).body(error);
}
// handleMethodArgumentNotValid : triggers when #Valid fails
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(err -> errors.put(err.getField(), err.getDefaultMessage()));
LOGGER.error("GlobalExceptionHandler | handleMethodArgumentNotValid", errors);
return ResponseEntity.badRequest()
.body(errors);
}
// handleMissingServletRequestParameter : triggers when there are missing parameters
#Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
List<String> details = new ArrayList<String>();
StringBuilder builder = new StringBuilder();
builder.append(ex.getParameterName());
details.add(builder.toString());
HttpStatus httpStatus = HttpStatus.valueOf(status.value());
ErrorDTO error = new ErrorDTO.ErrorDTOBuilder()
.timestamp(LocalDateTime.now())
.status(status.value())
.errorDetails(details)
.path(request.getContextPath())
.httpStatus(httpStatus)
.build();
LOGGER.error("GlobalExceptionHandler | handleMissingServletRequestParameter", error);
return ResponseEntity.status(status).body(error);
}
// handleHttpMessageNotReadable : triggers when the JSON is malformed
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
List<String> details = new ArrayList<String>();
StringBuilder builder = new StringBuilder();
builder.append(ex.getMessage());
details.add(builder.toString());
HttpStatus httpStatus = HttpStatus.valueOf(status.value());
ErrorDTO error = new ErrorDTO.ErrorDTOBuilder()
.timestamp(LocalDateTime.now())
.status(status.value())
.errorDetails(details)
.path(request.getContextPath())
.httpStatus(httpStatus)
.build();
LOGGER.error("GlobalExceptionHandler | handleHttpMessageNotReadable", error);
return ResponseEntity.status(status).body(error);
}
#ExceptionHandler(Exception.class)
public ErrorDTO handleGenericException(HttpServletRequest request, Exception ex) {
List<String> details = new ArrayList<String>();
details.add(ex.getMessage());
ErrorDTO error = new ErrorDTO.ErrorDTOBuilder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
.errorDetails(details)
.path(request.getServletPath())
.httpStatus(HttpStatus.INTERNAL_SERVER_ERROR)
.build();
LOGGER.error("GlobalExceptionHandler | handleGenericException", error);
return error;
}
}
Here is the error shown below.
{
"timestamp": "2023-02-17T12:56:02.341+00:00",
"status": 404,
"error": "Not Found",
"trace": "org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]\r\n\tat org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:273)\r\n\tat org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233)\r\n\tat org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:566)\r\n\tat org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:743)\r\n\tat org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711)\r\n\tat org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:654)\r\n\tat org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:407)\r\n\tat org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)\r\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)\r\n\tat org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752)\r\n\tat org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:703)\r\n\tat com.skyapi.weatherforecast.location.LocationService$$SpringCGLIB$$0.add(<generated>)\r\n\tat com.skyapi.weatherforecast.location.LocationApiController.addLocation(LocationApiController.java:21)\r\n\tat java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:578)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:731)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:177)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:859)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1734)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.base/java.lang.Thread.run(Thread.java:1589)\r\nCaused by: org.hibernate.exception.DataException: could not execute statement\r\n\tat org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:53)\r\n\tat org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:56)\r\n\tat org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:109)\r\n\tat org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:95)\r\n\tat org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:200)\r\n\tat org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3429)\r\n\tat org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:4058)\r\n\tat org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:103)\r\n\tat org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:612)\r\n\tat org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:483)\r\n\tat java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:729)\r\n\tat org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:480)\r\n\tat org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:329)\r\n\tat org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)\r\n\tat org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)\r\n\tat org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1425)\r\n\tat org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:477)\r\n\tat org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2234)\r\n\tat org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:1930)\r\n\tat org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:439)\r\n\tat org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:183)\r\n\tat org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:281)\r\n\tat org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)\r\n\tat org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:562)\r\n\t... 58 more\r\nCaused by: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Data too long for column 'code' at row 1\r\n\tat com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:104)\r\n\tat com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:916)\r\n\tat com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1061)\r\n\tat com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1009)\r\n\tat com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1320)\r\n\tat com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:994)\r\n\tat com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)\r\n\tat com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)\r\n\tat org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197)\r\n\t... 77 more\r\n",
"message": "could not execute statement; SQL [n/a]",
"path": "/v1/locations"
}
Here is the console output shown
org.springframework.web.HttpMediaTypeNotAcceptableException: No acceptable representation
It return this kind of exception message rather than ErrorDTO.
Here is the repo : Link
How can I fix the issue?
After adding #Data annotation to ErrorDTO, the issue disappeared.
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Data
public class ErrorDTO {
private LocalDateTime timestamp;
private int status;
private String path;
private HttpStatus httpStatus;
List<String> errorDetails;
}
I would like to simplify my code and merge common parts of the several methods into new one. Could you give me the piece of advice - what is the best way to do it?
My methods are:
#ExceptionHandler(value = {ProhibitedScimTypeException.class})
public ResponseEntity<ErrorDto> policyConflict(final ProhibitedScimTypeException exception) {
final var errorDto = new ErrorDto();
errorDto.setDetail(exception.getMessage());
errorDto.setStatus(BAD_REQUEST.toString());
errorDto.setScimType("prohibited");
return new ResponseEntity<>(errorDto, HttpStatus.BAD_REQUEST);
}
#ExceptionHandler(value = {UserAlreadyExistsException.class})
public ResponseEntity<ErrorDto> userNameExistsConflict(final UserAlreadyExistsException exception) {
final var errorDto = new ErrorDto();
errorDto.setDetail(exception.getMessage());
errorDto.setStatus(CONFLICT.toString());
errorDto.setScimType("uniqueness");
return new ResponseEntity<>(errorDto, HttpStatus.CONFLICT);
}
#ExceptionHandler(value = {UserNotFoundException.class})
public ResponseEntity<ErrorDto> userNameNotFoundConflict(final UserNotFoundException exception) {
final var errorDto = new ErrorDto();
errorDto.setDetail(exception.getMessage());
errorDto.setStatus(NOT_FOUND.toString());
errorDto.setScimType("prohibited");
return new ResponseEntity<>(errorDto, HttpStatus.NOT_FOUND);
}
I would like to separate the common part which is:
final var errorDto = new ErrorDto();
errorDto.setDetail(exception.getMessage());
errorDto.setStatus(MEHTOD.toString());
errorDto.setScimType("something");
can you extract the common part to a method like this?
private ResponseEntity<ErrorDto> conflict(final Throwable exception, HttpStatus status, String scrimType) {
final var errorDto = new ErrorDto();
errorDto.setDetail(exception.getMessage());
errorDto.setStatus(status.toString());
errorDto.setScimType(scrimType);
return new ResponseEntity<>(errorDto, status);
}
and call it from your methods,
#ExceptionHandler(value = {ProhibitedScimTypeException.class})
public ResponseEntity<ErrorDto> policyConflict(final ProhibitedScimTypeException exception) {
return conflict(exception, HttpStatus.BAD_REQUEST, "prohibited");
}
#ExceptionHandler(value = {UserAlreadyExistsException.class})
public ResponseEntity<ErrorDto> userNameExistsConflict(final UserAlreadyExistsException exception) {
return conflict(exception, HttpStatus.CONFLICT, "uniqueness");
}
#ExceptionHandler(value = {UserNotFoundException.class})
public ResponseEntity<ErrorDto> userNameNotFoundConflict(final UserNotFoundException exception) {
return conflict(exception, HttpStatus.NOT_FOUND, "prohibited");
}
Create a method:
private ResponseEntity<ErrorDto> conflict(final Throwable exception, final Object status, final String scimType, final HttpStatus httpStatus) {
final var errorDto = new ErrorDto();
errorDto.setDetail(exception.getMessage());
errorDto.setStatus(status.toString());
errorDto.setScimType(scimType);
return new ResponseEntity<>(errorDto, httpStatus);
}
and use it like
#ExceptionHandler(value = {ProhibitedScimTypeException.class})
public ResponseEntity<ErrorDto> policyConflict(final ProhibitedScimTypeException exception) {
return this.conflict(exception, BAD_REQUEST, "prohibited", HttpStatus.BAD_REQUEST);
}
I have requirements that if the JSON for the post request is invalid, I will need to send 400 HTTP response codes and if any fields are not parsable, the return status code will be 422. An example post request can be:
{
"amount": "12.3343",
"timestamp": "2018-07-17T09:59:51.312Z"
}
The Dto class is provided as below,
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class TransactionDto {
#NotNull
#Min(0)
private BigDecimal amount;
#NotNull
private LocalDateTime timestamp;
}
This is the controller with POST request,
#Slf4j
#RestController
#RequestMapping("/")
#Validated
public class TransactionController {
#Autowired
private TransactionService transactionService;
#Operation(description = "create a transaction using the provided JSON data")
#ApiResponses(value = {
#ApiResponse(responseCode = "201", description = "Create transaction", content = {
#Content(mediaType = "application/json", schema = #Schema(implementation = Transaction.class))}),
#ApiResponse(responseCode = "204", description = "Transaction is older than 60 seconds", content = #Content(mediaType = "application/json")),
#ApiResponse(responseCode = "500", description = MessageConstant.INTERNAL_SERVER_ERROR_MSG, content = #Content)})
#PostMapping(value = "/transactions")
public ResponseEntity<Object> createProperty(#RequestBody #Valid TransactionDto transactionDto) {
try {
final LocalDateTime transactionTimestamp = transactionDto.getTimestamp();
final LocalDateTime localDateTimeNow = LocalDateTime.now(ZoneOffset.UTC);
final long secondsDuration = Duration.between(transactionTimestamp, localDateTimeNow).toSeconds();
final boolean isFuture = transactionTimestamp.isAfter(localDateTimeNow);
if (isFuture) {
return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.UNPROCESSABLE_ENTITY,
"Transaction is in future"), new HttpHeaders(), HttpStatus.ACCEPTED);
}
if (secondsDuration > 60) {
return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.NO_CONTENT,
"Transaction is older than 60 seconds"), new HttpHeaders(), HttpStatus.NO_CONTENT);
}
final Transaction transaction = transactionService.createTransaction(transactionDto);
return new ResponseEntity<>(transaction, new HttpHeaders(), HttpStatus.CREATED);
} catch (Exception ex) {
log.error(MessageConstant.INTERNAL_SERVER_ERROR_MSG + ex.getMessage());
return new ResponseEntity<>(ApiResponseMessage.getInternalServerError(), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
If the "amount" is, say, "sfdfd", this is not BigDecimal, we should provide the 422. But if the "amount" is "-12.3343", this is a constraint validation error but the data is valid and parsable. So we can't have the 422.
This is my exception handling class,
#Slf4j
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#Override
#Nonnull
protected ResponseEntity<Object> handleMissingServletRequestParameter(
MissingServletRequestParameterException ex,
#Nonnull HttpHeaders headers,
#Nonnull HttpStatus status,
#Nonnull WebRequest request
) {
String error = ex.getParameterName() + " parameter is missing";
return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.BAD_REQUEST,
error), new HttpHeaders(), HttpStatus.BAD_REQUEST);
}
#Override
#Nonnull
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(
#Nonnull HttpMediaTypeNotSupportedException ex,
#Nonnull HttpHeaders headers,
#Nonnull HttpStatus status,
#Nonnull WebRequest request
) {
String message = prepareMessageFromException(ex, (ServletWebRequest) request);
return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.UNSUPPORTED_MEDIA_TYPE,
" media type is not supported. Supported media types "), new HttpHeaders(), HttpStatus.UNSUPPORTED_MEDIA_TYPE);
}
#Override
#NonNull
protected ResponseEntity<Object> handleMethodArgumentNotValid(
#NonNull MethodArgumentNotValidException ex,
#NonNull HttpHeaders headers,
#NonNull HttpStatus status,
#NonNull WebRequest request
) {
String message = prepareMessageFromException(ex, (ServletWebRequest) request);
ApiErrorResponse apiError = new ApiErrorResponse(BAD_REQUEST);
apiError.setMessage("Validation error");
apiError.addValidationErrors(ex.getBindingResult().getFieldErrors());
apiError.addValidationError(ex.getBindingResult().getGlobalErrors());
return buildResponseEntity(apiError);
}
#ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex) {
ApiErrorResponse apiError = new ApiErrorResponse(BAD_REQUEST);
apiError.setMessage("Validation error");
apiError.addValidationErrors(ex.getConstraintViolations());
return buildResponseEntity(apiError);
}
#ExceptionHandler(Exception.class)
#ResponseBody
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
ServletWebRequest req = (ServletWebRequest) request;
String message = prepareMessageFromException(ex, (ServletWebRequest) request);
log.info("{} to {}", req.getHttpMethod(), req.getRequest().getServletPath());
log.error(message, ex);
return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.BAD_REQUEST,
message), new HttpHeaders(), HttpStatus.BAD_REQUEST);
}
#Override
#Nonnull
protected ResponseEntity<Object> handleHttpMessageNotReadable(
#Nonnull HttpMessageNotReadableException ex,
#Nonnull HttpHeaders headers,
#Nonnull HttpStatus status,
#Nonnull WebRequest request
) {
String message = prepareMessageFromException(ex, (ServletWebRequest) request);
return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, BAD_REQUEST,
message), new HttpHeaders(), HttpStatus.BAD_REQUEST);
}
private String prepareMessageFromException(Exception ex, ServletWebRequest request) {
String message = ex.getMessage();
log.info("{} to {}", request.getHttpMethod(), request.getRequest().getServletPath());
log.error(message);
if (message != null && !message.isEmpty()) {
message = message.split(":")[0];
}
return message;
}
#Override
#Nonnull
protected ResponseEntity<Object> handleHttpMessageNotWritable(
#Nonnull HttpMessageNotWritableException ex,
#Nonnull HttpHeaders headers,
#Nonnull HttpStatus status,
#Nonnull WebRequest request
) {
String error = "Error writing JSON output";
return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR,
"Internal server error. please contact support !!"), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
#Override
#Nonnull
protected ResponseEntity<Object> handleNoHandlerFoundException(
NoHandlerFoundException ex,
#Nonnull HttpHeaders headers,
#Nonnull HttpStatus status,
#Nonnull WebRequest request
) {
return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.BAD_REQUEST,
String.format("Could not find the %s method for URL %s", ex.getHttpMethod(), ex.getRequestURL())), new HttpHeaders(), HttpStatus.BAD_REQUEST);
}
private ResponseEntity<Object> buildResponseEntity(ApiErrorResponse apiError) {
return new ResponseEntity<>(apiError, apiError.getStatus());
}
#ExceptionHandler(EntityNotFoundException.class)
protected ResponseEntity<Object> handleEntityNotFound(EntityNotFoundException ex) {
return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.NOT_FOUND,
"Resource not found: "), new HttpHeaders(), HttpStatus.NOT_FOUND);
}
#ExceptionHandler(DataIntegrityViolationException.class)
protected ResponseEntity<Object> handleDataIntegrityViolation(DataIntegrityViolationException ex, WebRequest request) {
if (ex.getCause() instanceof ConstraintViolationException) {
return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.CONFLICT,
"Database error"), new HttpHeaders(), HttpStatus.CONFLICT);
}
return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR,
"Internal server error. please contact support !!" + ex.getMessage()), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(MethodArgumentTypeMismatchException.class)
protected ResponseEntity<Object> handleMethodArgumentTypeMismatch(MethodArgumentTypeMismatchException ex, WebRequest request) {
return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.BAD_REQUEST,
String.format("The parameter '%s' of value '%s' could not be converted to type '%s'", ex.getName(), ex.getValue(), ex.getRequiredType())), new HttpHeaders(), HttpStatus.BAD_REQUEST);
}
}
At the moment, I get 400 in both of the cases mentioned. How do I refactor the code to get the correct response code?
I manage to achieve that after some modification of the existing code,
#Override
#Nonnull
protected ResponseEntity<Object> handleHttpMessageNotReadable(
#Nonnull HttpMessageNotReadableException ex,
#Nonnull HttpHeaders headers,
#Nonnull HttpStatus status,
#Nonnull WebRequest request
) {
String message = prepareMessageFromException(ex, (ServletWebRequest) request);
final Throwable throwableCause = ex.getCause();
if (throwableCause instanceof InvalidFormatException) {
return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.UNPROCESSABLE_ENTITY,
message), new HttpHeaders(), HttpStatus.UNPROCESSABLE_ENTITY);
}
return new ResponseEntity<>(ApiResponseMessage.getGenericApiResponse(Boolean.FALSE, HttpStatus.BAD_REQUEST,
message), new HttpHeaders(), HttpStatus.BAD_REQUEST);
}
So when the throwable is InvalidFormatException, I will return the 422 response status code and other the method works as before.
I can't seem to be able to make the following code work.
For some reason neither of the following advice is being thrown to the controller.
#ControllerAdvice
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {
#Autowired
private MessageSource messageSource;
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String mensagemUsuario = messageSource.getMessage("mensagem.invalida", null, LocaleContextHolder.getLocale());
String mensagemDesenvolvedor = ex.getCause().toString();
List<Erro> erros = Arrays.asList(new Erro(mensagemUsuario, mensagemDesenvolvedor));
return handleExceptionInternal(ex, erros, headers, HttpStatus.BAD_REQUEST, request);
}
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
List<Erro> erros = criarListaDeErros(ex.getBindingResult());
return handleExceptionInternal(ex, erros, headers, HttpStatus.BAD_REQUEST, request);
}
private List<Erro> criarListaDeErros(BindingResult bindingResult) {
List<Erro> erros = new ArrayList<>();
for (FieldError fieldError : bindingResult.getFieldErrors()) {
String mensagemUsuario = messageSource.getMessage(fieldError, LocaleContextHolder.getLocale());
String mensagemDesenvolvedor = fieldError.toString();
erros.add(new Erro(mensagemUsuario, mensagemDesenvolvedor));
}
return erros;
}
#org.springframework.web.bind.annotation.ExceptionHandler({EmptyResultDataAccessException.class})
#ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<Object> handleEmptyResultDataAccessException(RuntimeException ex, WebRequest request){
String mensagemUsuario = messageSource.getMessage("mensagem.invalida", null, LocaleContextHolder.getLocale());
String mensagemDesenvolvedor = ex.getCause().toString();
List<Erro> erros = Arrays.asList(new Erro(mensagemUsuario, mensagemDesenvolvedor));
return handleExceptionInternal(ex, erros, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
}
#org.springframework.web.bind.annotation.ExceptionHandler({DataIntegrityViolationException.class})
public ResponseEntity<Object> handleDataIntegrityViolationException(DataIntegrityViolationException ex, WebRequest webRequest) {
String mensagemUsuario = messageSource.getMessage("mensagem.invalida", null, LocaleContextHolder.getLocale());
String mensagemDesenvolvedor = ex.getCause().toString();
List<Erro> erros = Arrays.asList(new Erro(mensagemUsuario, mensagemDesenvolvedor));
return handleExceptionInternal(ex, erros, new HttpHeaders(), HttpStatus.BAD_REQUEST, webRequest);
}
public static class Erro {
private String mensagemUsuario;
private String mensagemDesenvolvedor;
public Erro(String mensagemUsuario, String mensagemDesenvolvedor) {
this.mensagemUsuario = mensagemUsuario;
this.mensagemDesenvolvedor = mensagemDesenvolvedor;
}
} }
Project Structure :
It's my first time creating controller advice this way so would very much appreciate if someone could point me what I'm doing wrong.
Thank you.
I'm trying to override default error messages in BindingResult. I implemented Controller advice like this
#ControllerAdvice
public final class DefaultControllerAdvice {
private static final String ERROR_MESSAGE_FORMATTER = " - ";
#ExceptionHandler(BindException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public List<String> handleValidationException(final BindException exception) {
return exception.getBindingResult()
.getAllErrors()
.stream()
.filter(error -> error instanceof FieldError)
.map(objectError -> (FieldError) objectError)
.map(errorMessageFormatter)
.collect(Collectors.toList());
}
private final Function<FieldError, String> errorMessageFormatter =
error -> error.getField() + ERROR_MESSAGE_FORMATTER + error.getDefaultMessage();
}
and my controller
#PostMapping(value = "/register")
private String postRegistration( #ModelAttribute #Valid final UserCreateFormDto user, final BindingResult result,
final RedirectAttributes redirectAttributes, final WebRequest webRequest) {
try {
if (result.hasErrors()) {
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.user", result);
redirectAttributes.addFlashAttribute("user", user);
throw new BindException(result);
}
if (userService.checkEmailExist(user.getEmail())) {
throw new UserNotExistsException("User with email: "+ user.getEmail()+" already exists.");
}
final User registered = userService.createNewUserAccount(user);
final String appUrl = webRequest.getContextPath();
eventPublisher.publishEvent(
new RegistrationCompleteEvent(registered, webRequest.getLocale(), appUrl));
return "redirect:/login?success";
} catch (UserNotExistsException error) {
return "redirect:/register?exists";
} catch (BindException error) {
return "redirect:/register";
}
}
and test case
#Test
public void shouldNotCreateUserWhenUsernameIsEmpty() throws Exception {
//given
final UserCreateFormDto userCreateFormDto = createUserCreateForm();
final User user = createUser();
given(userService.checkEmailExist(userCreateFormDto.getEmail())).willReturn(false);
given(userService.createNewUserAccount(any(UserCreateFormDto.class))).willReturn(user);
//when
final MvcResult response = mockMvc
.perform(post("/register").with(csrf())
.contentType(MediaType.MULTIPART_FORM_DATA)
.param("username","")
.param("email",userCreateFormDto.getEmail())
.param("password", Arrays.toString(userCreateFormDto.getPassword()))
.param("matchingPassword", Arrays.toString(userCreateFormDto.getMatchingPassword())))
.andReturn();
//then
assertThat(response.getFlashMap().isEmpty()).isFalse();
assertThat(response.getResponse().getStatus()).isEqualTo(HttpStatus.FOUND.value());
assertThat(response.getResponse().getRedirectedUrl()).isEqualTo("/register");
verify(userService, times(1)).checkEmailExist(userCreateFormDto.getEmail());
verify(userService, times(0)).createNewUserAccount(any(UserCreateFormDto.class));
my question is how to get the bindingResult default error message?
I would like to test what an error message is getting while validate input fields.