I have a post-interceptor. When the control layer is executed and returns information, the post-interceptor will be executed. At this time, an exception in the post-interceptor will be caught by the global exception handling and a prompt message will be returned. Use "postman" to test and control The information of layer and global exception handling is returned at the same time. Is this really returned? I wrote a test example. In the same situation, only the information of the control layer is returned. I think it should return the information of global exception handling.
Controller
#RestController
#RequestMapping("/v1/book")
#Validated
public class BookController {
private final BookService bookService;
public BookController(BookService bookService) {
this.bookService = bookService;
}
#GetMapping("/search")
public R searchBook(#RequestParam(value = "q", required = false, defaultValue = "") String q) {
return R.select(bookService.getBookByKeyword(q));
}
}
Interceptor
public class LogInterceptor extends HandlerInterceptorAdapter {
public LogInterceptor(LoggerResolver loggerResolver) {
this.loggerResolver = loggerResolver;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
// There will be a runtime exception here
}
}
Global Exception Handing
#Order
#RestControllerAdvice
#Slf4j
public class RestExceptionHandler {
/**
* Exception
*/
#ExceptionHandler({Exception.class})
public R processException(Exception exception) {
log.error("", exception);
return R.error();
}
}
Result
{
"code": 200,
"data": [
// ...
],
"type": "success",
"message": "OK"
}{
"code": 500,
"type": "error",
"message": "Internal Server Error"
}
"R extends HashMap<String, Object>", used to unify the return structure.
looking at your code snippet, I'm not sure what are those R in the searchBook and processException
try this (edit the processException to meet your specs):
#GetMapping("/search")
public ResponseEntity<?> searchBook(#RequestParam(value = "q", required = false, defaultValue = "") String q) {
return new ResponseEntity<>(bookService.getBookByKeyword(q), HttpStatus.OK);
}
#ExceptionHandler({Exception.class})
public ResponseEntity<?> processException(Exception exception) {
return new ResponseEntity<>(new ErrorDTO(exception.getMessage()), HttpStatus.UNPROCESSABLE_ENTITY);
}
Related
Controller Method
#PostMapping("/hello")
public Hello hello(#Valid #RequestBody Hello hello) {
return hello;
}
POJO
import jakarta.validation.constraints.NotBlank;
class Hello{
#NotBlank(message = "msg must be present")
String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
Upon hitting the above URL with the following payload
{
"msg":""
}
I am getting the following response.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "Invalid request content.",
"instance": "/hello"
}
It should ideally specify the message msg must be present.
What's wrong here?
The following things have been already tried
added server.error.include-message: always in application.properties file
#ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Object> handleConstraintViolationException(ConstraintViolationException e) {
return new ResponseEntity<Object>("ConstraintViolationException",
HttpStatus.BAD_REQUEST);
}
Thanks in advance đź‘Ź
Edit
I had a #RestControllerAdvice and it starts working fine, once i remove it. #RestControllerAdvice is needed in my case for the customization of exceptions.
You have to write a controller advice and return the interpolated message from the exception caught in handler for invalid method argument.
#RestControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
String bodyOfResponse = ex.getBindingResult().getFieldErrors().get(0).getDefaultMessage();
return new ResponseEntity(bodyOfResponse, HttpStatus.BAD_REQUEST);
}
}
And after that you should get back message msg must be present along with 400. In case you have more constraints, you should iterate over field errors, and get(0) is only for demonstration purpose.
we are using reactive feign client (com.playtika.reactivefeign:feign-reactor-spring-cloud-starter:3.2.0)
circuit breaker version : org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j:2.1.0
and spring boot application version org.springframework.boot’ version ’2.6.6
when we get an error from reactive feign client (such as 404 error)
#ReactiveFeignClient(name = "someRestClient", url = "${react-gpi-service.url}",configuration = AuthConfigurationsomeRestClient.class, fallbackFactory = someRestClienttFallbackFactory.class)
#Profile("!test")
public interface someRestClient {
#PostMapping(value = "/v2/{entity}/any", produces = MediaType.ALL_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
Mono<String> any(#PathVariable(value = "entity")
it goes over the error decoder to check if it should be retried
#Slf4j
#RequiredArgsConstructor
public class RetryableErrorDecoder implements ErrorDecoder {
private static ErrorDecoder defaultErrorDecoder = new Default();
private final String clientName;
public Exception decode(String methodKey, Response response) {
String body = "";
try {
body = IOUtils.toString(response.body().asInputStream(), StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("failed to parse error response body", e);
}
log.error("In RetryableErrorDecoder, got an error from {}. status: {}, body: {}, reason: {}, request: {}",
clientName, response.status(), body, response.reason(), response.request());
if (response.status() == HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE ||
response.status() == HttpStatusCodes.STATUS_CODE_BAD_GATEWAY) {
log.warn("Retry on error 503 or 502");
return createRetryableException(response, "Service Unavailable 503, 502");
} else {
Exception decode = defaultErrorDecoder.decode(methodKey, response);
if (decode instanceof FeignException &&
decode.getMessage().contains("authorizing")) {
log.warn("Retry on {}", decode.getMessage());
return createRetryableException(response, "Service authorizing problem");
}
return decode;
}
}
private Exception createRetryableException(Response response, String message) {
return new RetryableException(
response.status(),
message,
response.request().httpMethod(),
null,
null,
response.request());
}
}
after that it goes to Circuit beaker predicate
public class someFailurePredicate implements Predicate<Throwable> {
#Override
public boolean test(Throwable throwable) {
return throwable instanceof ThirdPartyException
|| throwable instanceof ReadTimeoutException
|| throwable instanceof OutOfRetriesException;
}
}
and then it goes to fallBackFactory mechanism because the circuit breaker requires the fallback method so the circuit breaker predicate is activated again.
#Component
public class someRestClientFallbackFactory implements FallbackFactory<someRestClient> {
#Override
public someRestClient apply(Throwable throwable) {
return new someRestClientFallback(throwable);
}
}
public class someRestClientFallback implements someRestClient {
private final Throwable cause;
public someClientFallback(Throwable cause) {
this.cause = cause;
}
public Mono<String> performSearchRequest(String entity,
) {
return Mono.error(cause);
}
}
because we have 2 mechanisms of error handling the circuit predicate is calling twice and duplicating the error.
I tried to move the retry mechanism(error decoder) to fallback method but the fallbackfactory method accepts throwable and reactiveFeignClientException doesn't have a status code and it's hard to determine if we should do the retry.
if I remove the fallback method I get this error message :
org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException: No fallback available.
we need to add it but then we have two mechanisms and a duplicate circuit breaker predicate count
Reactive Feign Client enables its own CB by default, it is possible to disable it by setting reactive.feign.circuit.breaker.enabled to false - https://github.com/PlaytikaOSS/feign-reactive/blob/develop/feign-reactor-spring-configuration/README.md
I try to implement a custom error response in a spring boot rest application for 404 errors.
I read many of the solutions presented in stackoverflow, without success.
When I call an invalid entry point I obtain this result:
{
"timestamp": "2022-06-22T10:38:41.114+00:00",
"status": 404,
"error": "Not Found",
"path": "/ws-facturx/fx2"
}
But i'd like to have a response that should look like this:
{
"operationId": "u044eZg2gHwtadqxB5CVv6aeMBjj0w",
"status": "ERROR",
"operation": "webserviceName",
"clientName": "ACME Inc",
"errorMessage": "Error message from Server",
"createdAt": "2022-06-22T09:15:04.844+00:00"
}
I first tried to use #RestControllerAdvice to intercept the exception when they are thrown.
#ExceptionHandler(value = {AppServiceException.class, NoHandlerFoundException.class, ServletServiceException.class })
public ResponseEntity<Object> handleAppServiceException(Exception ex,
WebRequest req) throws JsonProcessingException {
FacturxDto request = context.getFacturxDtoContext();
ErrorMessage errorMessage = errorMessageBuilder(request, ex);
return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
I also modified my application.properties :
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false
If i call a non defined entry point I do not reach this method. I tried to use an interceptor.
I firs added a class for adding interceptor to InterceptorRegistry:
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final ApplicationExchangeContext context;
public WebMvcConfig(ApplicationExchangeContext context) {
this.context = context;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ApplicationInterceptor(context)).addPathPatterns("/**");
}
}
My ApplicationInterception looks like this:
#Component
public class ApplicationInterceptor implements HandlerInterceptor {
private final ApplicationExchangeContext context;
#Autowired
public ApplicationInterceptor(ApplicationExchangeContext context) {
this.context = context;
}
//unimplemented methods comes here. Define the following method so that it
//will handle the request before it is passed to the controller.
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (response.getStatus() == HttpStatus.NOT_FOUND.value()) {
// si on a un 404
System.out.println(handler);
String requestData = request.getReader().lines().collect(Collectors.joining());
System.out.println(requestData);
Gson gson = new Gson();
FacturxDto facturxDto = gson.fromJson(requestData, FacturxDto.class);
context.setFacturxDtoContext(facturxDto);
throw new ServletServiceException("404...");
}
System.out.println("Done in preHandle");
return true;
// return HandlerInterceptor.super.preHandle(request, response, handler);
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
System.out.println(request);
System.out.println(response);
if (response.getStatus() == HttpStatus.NOT_FOUND.value()) {
// si on a un 404
System.out.println(handler);
String requestData = request.getReader().lines().collect(Collectors.joining());
System.out.println(requestData);
Gson gson = new Gson();
FacturxDto facturxDto = gson.fromJson(requestData, FacturxDto.class);
context.setFacturxDtoContext(facturxDto);
throw new ServletServiceException("404...");
}
System.out.println("Done in afterCompletion");
}
}
On the preHandle, i do reach the catch part of the code block but i do not access the RestControllerAdvice method that should handle this exception and build my expected object.
The exception is thrown. But i do not return it to user. Instead I do have an HTML page.
when i am trying to get values by id ..i got error like this in postman
{
"timestamp": 1547708533031,
"status": 500,
"error": "Internal Server Error",
"exception":"org.springframework.http.converter.HttpMessageNotWritableException",
"message": "Could not write JSON: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS); nested exception is com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.Cetegory.Entites.ArtistRegister[\"subcetgory\"]->com.Cetegory.Entites.SubCetegory_$$_jvst706_1[\"handler\"])",
"path": "/getartist/2"
}
this is contoller for get data by id
#RestController
public class RegisterController {
#Autowired
ArtistService artser;
#PostMapping(value="/addArtist",produces="application/json")
#ResponseBody public ArtistRegister addArtist(#RequestBody ArtistRegister artist) {
ArtistRegister artRegister = null;
try {
artRegister = artser.addArtist(artist);
} catch (Exception e) {
e.printStackTrace();
}
return artRegister;
}
#RequestMapping(value="/getartist/{artist_id}",method=RequestMethod.GET,produces="application/json")
#ResponseBody public ArtistRegister getArtistDetails(#PathVariable ("artist_id") int artist_id ,HttpServletRequest request,
HttpServletResponse response) throws Exception{
return artser.getArtistDetails(artist_id);
}
#RequestMapping(value="/delete/{artist_id}",method=RequestMethod.DELETE,produces="application/json")
public void deleteById(#PathVariable (value="artist_id") int artist_id,HttpServletRequest request,
HttpServletResponse response) throws Exception{
artser.deleteById(artist_id);
}
#RequestMapping(value = "/updateartist", method = RequestMethod.PUT, produces = "application/json")
public ArtistRegister updateArtist(#RequestBody ArtistRegister artreg, HttpServletRequest request, HttpServletResponse response)
throws Exception
{
return artser.updateArtist(artreg);
}
this is service
#Service
#Transactional
public class ArtistService {
#Autowired
private ArtistRepository artrep;
#Autowired
private RegisterDAO artdao;
public ArtistRegister addArtist(ArtistRegister artreg) {
ArtistRegister artReg = null;
try {
artReg = artrep.save(artreg);
} catch (Exception e) {
e.printStackTrace();
}
return artReg;
}
public ArtistRegister getArtistDetails(int artist_id) {
return artdao.getArtistDetails(artist_id);
}
public void deleteById(int artist_id) {
artdao.deleteById(artist_id);
}
public ArtistRegister updateArtist(ArtistRegister artreg) {
return artdao.updateArtist(artreg);
}
}
this is DAO
#Repository
#Transactional
public class RegisterDAO {
private static final Logger logger = LoggerFactory.getLogger(SubCetegoryDAO.class);
#Autowired
SessionFactory sessionFactory;
#Autowired
EntityManager entitymanager;
public ArtistRegister getArtistDetails(int artist_id) {
try
{
String hql = "FROM ArtistRegister a where a.artist_id=?";
return (ArtistRegister) entitymanager.createQuery(hql).setParameter(1, artist_id).getSingleResult();
}
catch (EmptyResultDataAccessException e)
{
return null;
}
catch (Exception e)
{
logger.error("Exception in getUser"+ e.getMessage());
return null;
}
}
Remove #JsonManagedReference annotation and update fetch type to LAZY, by following way:
#OneToOne(targetEntity = SubCetegory.class, cascade = CascadeType.MERGE,fetch=FetchType.LAZY)
#JoinColumn(name = "sub_cetegory_id")
What are fetch types Lazy and Eager?
The EAGER strategy is a requirement on the persistence provider runtime that data must be eagerly fetched. The LAZY strategy is a hint to the persistence provider runtime that data should be fetched lazily when it is first accessed.
REFERENCES
Official documentation fetch type
validate Rest URL in spring boot.
Requirement: If I hit the wrong URL then it should throw a custom exception.
ex. Correct URL is "/fulfillment/600747l/send_to_hub" If I hit "/api/600747l/send_to_hub_1" then it should return exception like
"404:- URL not Found.".
Right now it returning "500 : -
{
"timestamp": 1531995246549,
"status": 500,
"error": "Internal Server Error",
"message": "Invalid Request URL.",
"path": "/api/600747l/send_to_hub_1"
}"
you need to write NewClass with annotation #ControllerAdvice which will redirect all exceptions to this NewClass.
example
Your Custom Exception Class:
#Data
#AllArgsConstructor
#EqualsAndHashCode(callSuper = false)
public class IOApiException extends IOException {
private ErrorReason errorReason;
public IOApiException(String message, ErrorReason errorReason) {
super(message);
this.errorReason = errorReason;
}
}
Now the CustomExceptionHandler Class -
#ControllerAdvice
#RestController
public class GlobalExceptionHandler {
Logger logger = LoggerFactory.getLogger(this.getClass());
#ResponseStatus(HttpStatus.UNAUTHORIZED)
#ExceptionHandler(value = IOApiException.class)
public GlobalErrorResponse handleException(IOApiException e) {
logger.error("UNAUTHORIZED: ", e);
return new GlobalErrorResponse("URL Not Found", HttpStatus.UNAUTHORIZED.value(), e.getErrorReason());
}
//this to handle customErrorResponseClasses
public GlobalErrorResponse getErrorResponseFromGenericException(Exception ex) {
if (ex == null) {
return handleException(new Exception("INTERNAL_SERVER_ERROR"));
}
else if (ex instanceof IOApiException) {
return handleException((IOApiException) ex);
}
}
Now Your error response class:
public class GlobalErrorResponse {
private String message;
#JsonIgnore
private int statusCode;
private ErrorReason reason;
}
ErrorReason Class
public enum ErrorReason {
INTERNAL_SERVER_ERROR,
INVALID_REQUEST_PARAMETER,
INVALID_URL
}
add and register one filter who calls the GlobalExceptionHandler in exception case like this
public class ExceptionHandlerFilter implements Filter {
private final GlobalExceptionHandler globalExceptionHandler;
public ExceptionHandlerFilter(GlobalExceptionHandler globalExceptionHandler) {
this.globalExceptionHandler = globalExceptionHandler;
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (Exception exception) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
GlobalErrorResponse errorResponse = globalExceptionHandler.getErrorResponseFromGenericException(exception);
httpResponse.setStatus(errorResponse.getStatusCode());
response.getWriter().write(new ObjectMapper().writeValueAsString(errorResponse));
}
}
#Override
public void destroy() {
}
}
Like this you can add as many exceptions you want.. and can handle it manually.
As per your question first of all you need to define a base url(e.g.-/api) so that any url must be handled through your controller.Now after base url as shown /api/600747l/send_to_hub_1 #PathVariable int id. This circumstance is important, because Spring documentation said that if method argument annotated with #PathVariable can’t be casted to specified type (in our case to int), it will be exposed as String. Hence it can cause a TypeMismatchException.
To handle this I will use #ExceptionHandler annotation on #Controller level. Such approach suits for this situation as no one else. I just need to make 2 changes in the Controller:
1.Add MessageSource field
2.Add exception handler method
#Autowired
private MessageSource messageSource;
...
#ExceptionHandler(TypeMismatchException.class)
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorInfo handleTypeMismatchException(HttpServletRequest req, TypeMismatchException ex) {
Locale locale = LocaleContextHolder.getLocale();
String errorMessage = messageSource.getMessage("error.bad.smartphone.id", null, locale);
errorMessage += ex.getValue();
String errorURL = req.getRequestURL().toString();
return new ErrorInfo(errorURL, errorMessage);
}
...