My Spring Boot Service will do a job and exit after success with 0 (there is no restcontroller), but i want it aslo to exit on every exception so i added #ControllerAdvice on a class and put this method:
#ControllerAdvice
#RequiredArgsConstructor
#Slf4j
public class ImportInekData {
final InekService inekService;
final ImportDataService dataService;
public void doTheJob(){
log.info("Fetching new list from Inek.");
UpdatedList updatedList = inekService.getUpdatedList();
List<Standort> toBeUpdated = updatedList.getToBeUpdated();
List<String> toBeDeleted = updatedList.getToBeDeleted();
log.info("List fetched with " + toBeUpdated.size() + " valid entries to be updated and " + toBeDeleted.size() + " entries marked for deletion. ");
log.info("Pushing to DB...");
dataService.importAll(toBeUpdated);
}
#EventListener
public void onStart(ContextStartedEvent start){
log.info("Application started.");
doTheJob();
log.info("Import finished.");
SpringApplication.exit(start.getApplicationContext(), () -> 0);
}
#ExceptionHandler(value = Exception.class)
public String outOnException(Exception e){
log.error("Exception occurred see logs. Stopping..");
SpringApplication.exit(context, () -> -1);
return "dying";
}
}
All is working fine but when i throw an IllegalArgumentException the #ExceptionHandler method is not called. First i had a void method with no parameter and then i began trying with String return and at least one parameter - that is not needed.
How get this working? Is there a better way for my case to react on every exception?
Controller Advices in spring is a mechanism intended to properly handle the Exceptions at the level of spring MVC.
Spring MVC in a nutshell is a web framework, and as such, it assumes that you have some kind of web endpoint that is called by the end user or maybe frontend. This endpoint is an "entry-point" to your backend code that can have services, query the database, and so forth. If during this backend flow the exception is thrown in general you don't want that the web endpoint will return 500 internal server error, so spring provides tooling for convenient mapping of these exceptions: translating them to json with a "good-looking" message, with correct HTTP code, and so forth.
If you don't have any controllers, then the whole concept of controller advices is not applicable in your flow, so there is no point in using it...
Now the real question is what exactly do you want to achieve with this exception handling?
If Application context cannot start usually spring boot application will be closed gracefully...
If you want to close the application programmatically, make sure you've read this thread
Related
When using Spring Boot, I am unsure if error handling is already being taken care of by the Spring Framework, or if I have to implement it. For example, consider a controller method, which handles a DELETE request:
#DeleteMapping("/{studentId}")
public ResponseEntity<Long> deleteProfilePicture(#PathVariable Long studentId) {
return ResponseEntity.ok(profilePictureService.deleteprofilePictureByStudentId(studentId));
}
Is this fine, or should I instead wrap it inside a try-catch block:
#DeleteMapping("/{studentId}")
public ResponseEntity<Long> deleteProfilePicture(#PathVariable Long studentId) throws Exception {
try {
profilePictureService.deleteProfilePictureByStudentId(studentId));
} catch (DataAccessException e) {
throw new Exception("cannot delete profile picture of student: " + studentId);
}
}
Also: If I let my method deleteProfilePicture throw this Exception, who is handling the Exception? This must somehow be taken care of by Spring Boot, since it is possible to implement it without yielding any errors. Anyhow, what is the correct way of error handling in this scenario?
Spring Boot will turn the exception into an error response to the caller of the REST API. This does not mean that you shouldn't implement your own error handling logic, which you definitely should. As an example, you could use #ControllerAdvice to have a global exception handling for your application. Something along the following lines:
#ControllerAdvice
#Slf4j
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {Exception.class})
public ResponseEntity<Object> handleGenericExceptions(Exception exception, WebRequest webRequest) {
log.error("Handling: ", exception);
HttpStatus errorCode = HttpStatus.INTERNAL_SERVER_ERROR;
return this.handleExceptionInternal(exception, new ErrorInfo(errorCode.value(), "Unexpected Internal Server Error"), new HttpHeaders(), errorCode, webRequest);
}
}
You can read more about error handling in Spring Boot at https://www.baeldung.com/exception-handling-for-rest-with-spring.
I was reading the article - https://www.baeldung.com/exception-handling-for-rest-with-spring
which says
Spring 5 introduced the ResponseStatusException class.
We can create an instance of it providing an HttpStatus and optionally
a reason and a cause:
I started implementing it , and the code is
custom exception
#ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Actor Not Found")
public class ActorNotFoundException extends Exception {
private static final long serialVersionUID = 1L;
public ActorNotFoundException(String errorMessage) {
super(errorMessage);
}
}
method in service
public String updateActor(int index, String actorName) throws ActorNotFoundException {
if (index >= actors.size()) {
throw new ActorNotFoundException("Actor Not Found in Repsoitory");
}
actors.set(index, actorName);
return actorName;
}
controller
#GetMapping("/actor/{id}")
public String getActorName(#PathVariable("id") int id) {
try {
return actorService.getActor(id);
} catch (ActorNotFoundException ex) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Actor Not Found", ex); //agreed it could be optional, but we may need original exception
}
}
repo:
https://github.com/eugenp/tutorials/tree/master/spring-5/src/main/java/com/baeldung/exception
Question:
why ResponseStatusException in controller again has to specify reason - "Actor Not Found" ?, as the service already said - ""Actor Not Found in Repsoitory"
What is the proper way to adapt to ResponseStatusException model?
It looks like a mistake. Ideally the service shouldn't use any HTTP code, so I would remove the annotation in ActorNotFoundException. Everything else seems fine, the exception is caught in the controller and ResponseStatusException is thrown which is good, because it's a proper layer to put HTTP stuff.
Overall it is better to use #ControllerAdvice instead of ResponseStatusException. it gives you a unified exception handling solution. Although it is not a good idea from a design point of view, ResponseStatusException can help you to avoid creating your custom exceptions and use it at the service level to throw in case of an Exception.
to avoid writing the message again you can use the message that is already available in thrown exception:
throw new ResponseStatusException(HttpStatus.NOT_FOUND, ex.getMessage() , ex);
for examples and more info you can refer to the following articles:
Spring Boot Exception Handling — #ControllerAdvice
Spring Boot Exception Handling — ResponseStatusException
I am working on a Spring application that serves up REST endpoints. One of the endpoints essentially acts as a proxy between the HTML client and a third party cloud storage provider. This endpoint retrieves files from the storage provider and proxies them back to the client. Something like the following (note there is a synchronous and asynchronous version of the same endpoint):
#Controller
public class CloudStorageController {
...
#RequestMapping(value = "/fetch-image/{id}", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public ResponseEntity<byte[]> fetchImageSynchronous(#PathVariable final Long id) {
final byte[] imageFileContents = this.fetchImage(id);
return ResponseEntity.ok().body(imageFileContents);
}
#RequestMapping(value = "/fetch-image-async/{id}", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public Callable<ResponseEntity<byte[]>> fetchImageAsynchronous(#PathVariable final Long id) {
return () -> {
final byte[] imageFileContents = this.fetchImage(id);
return ResponseEntity.ok().body(imageFileContents);
};
}
private byte[] fetchImage(final long id) {
// fetch the file from cloud storage and return as byte array
...
}
...
}
Due to the nature of the client app (HTML5 + ajax) and how this endpoint is used, user authentication is supplied to this endpoint differently that the other endpoints. To handle this, a HandlerInterceptor was developed to deal with authentication for this endpoint:
#Component("cloudStorageAuthenticationInterceptor")
public class CloudStorageAuthenticationInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) {
// examine the request for the authentication information and verify it
final Authentication authenticated = ...
if (authenticated == null) {
try {
pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
} catch (IOException e) {
throw new RuntimeException(e);
}
return false;
}
else {
try {
request.login(authenticated.getName(), (String) authenticated.getCredentials());
} catch (final ServletException e) {
throw new BadCredentialsException("Bad credentials");
}
}
return true;
}
}
The interceptor is registered like this:
#Configuration
#EnableWebMvc
public class ApiConfig extends WebMvcConfigurerAdapter {
#Autowired
#Qualifier("cloudStorageAuthenticationInterceptor")
private HandlerInterceptor cloudStorageAuthenticationInterceptor;
#Override
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(this.cloudStorageAuthenticationInterceptor)
.addPathPatterns(
"/fetch-image/**",
"/fetch-image-async/**"
);
}
#Override
public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(this.asyncThreadPoolCoreSize);
executor.setMaxPoolSize(this.asyncThreadPoolMaxSize);
executor.setQueueCapacity(this.asyncThreadPoolQueueCapacity);
executor.setThreadNamePrefix(this.asyncThreadPoolPrefix);
executor.initialize();
configurer.setTaskExecutor(executor);
super.configureAsyncSupport(configurer);
}
}
Ideally, the image fetching would be done asynchronously (using the /fetch-image-asyc/{id} endpoint) because it has to call a third party web service which could have some latency.
The synchronous endpoint (/fetch-image/{id}) works correctly for all browsers. However, if using the asynchronous endpoint (/fetch-image-async/{id}), Chrome and Firefox work as expect.
However, if the client is Microsoft IE or Microsoft Edge, we seem some strange behavior. The endpoint is called correctly and the response sent successfully (at least from the server's viewpoint). However, it seems that the browser is waiting for something additional. In the IE/Edge DevTools window, the network request for the image shows as pending for 30 seconds, then seems to timeout, updates to successful and the image is successfully display. It also seems the connection to the server is still open, as the server side resources like database connections are not released. In the other browsers, the async response is received and processed in a second or less.
If I remove the HandlerInterceptor and just hard-wire some credentials for debugging, the behavior goes away. So this seems to have something to with the interaction between the HandlerInterceptor and the asynchronous controller method, and is only exhibited for some clients.
Anyone have a suggestion on why the semantics of IE/Edge are causing this behavior?
Based on your description, there are some different behaviors when using IE or Edge
it seems that the browser is waiting for something additional
the connection seems still open
it works fine if remove HandlerInterceptor and use hard code in auth logic
For the first behavior, I would suggest you use fiddler to trace all http requests. It is better if you could compare two different actions via fiddler (1) run on chrome, 2) run on edge ). Check all http headers in requests and responses carefully to see whether there is some different part. For the other behaviors, I would suggest you write logs to find which part spend the most time. It will provide you useful information to troubleshot.
After much tracing on the server and reading through the JavaDocs comments for AsyncHandlerInterceptor, I was able to resolve the issue. For requests to asynchronous controller methods, the preHandle method of any interceptor is called twice. It is called before the request is handed off to the servlet handling the request and again after the servlet has handled the request. In my case, the interceptor was attempting to authenticate the request for both scenarios (pre and post request handling). The application's authentication provider checks credentials in a database. For some reason if the client is IE or Edge, the authentication provider was unable to get a database connection when called from preHandle in the interceptor after the servlet handled the request. The following exception would be thrown:
ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataAccessResourceFailureException: Could not open connection; nested exception is org.hibernate.exception.JDBCConnectionException: Could not open connection] with root cause
java.sql.SQLTransientConnectionException: HikariPool-0 - Connection is not available, request timed out after 30001ms.
So the servlet would successfully handle the request and send a response, but the filter would get hung up for 30 seconds waiting for the database connection to timeout on the post processing called to preHandle.
So for me, the simple solution was to add a check in preHandle if it is being called after the servlet has already handled the request. I updated the preHandle method as follows:
#Override
public boolean preHandle(final HttpServletRequest pRequest, final HttpServletResponse pResponse, final Object pHandler) {
if (pRequest.getDispatcherType().equals(DispatcherType.REQUEST)) {
... perform authentication ...
}
return true;
}
That solved the issue for me. It doesn't explain everything (i.e., why only IE/Edge would cause the issue), but it seems that preHandle should only do work before the servlet handles the request anyways.
I'm using the Spring MVC asyncronous processing but the controller does not return a view on web browser.
#RequestMapping(value = "/generateGM", method = RequestMethod.POST)
public Callable<ModelAndView> generateGMReport(#RequestParam("countryCode") int countryCode, ModelAndView mv) {
Callable<ModelAndView> c = new GenericCallable(countryCode, reportDao, mv);
return c;
}
#Override
public ModelAndView call() throws Exception {
List<CostReport> gmList = reportDao.generateGrossMarginReport(countryCode);
mv.setViewName("gmReport");
mv.addObject("gmList", gmList);
return mv;
}
I had tried to modify the code to return Callable but it still does not return to the specified view name.
I'm using JBoss 7.1 as.
There is warning during deployment :
WARN [org.jboss.as.ee] (MSC service thread 1-7)
JBAS011006: Not installing optional component
org.springframework.web.context.request.async.StandardServletAsyncWebRequest
due to exception: org.jboss.as.server.deployment.DeploymentUnitProcessingException:
JBAS011054:
Could not find default constructor for class
org.springframework.web.context.request.async.StandardServletAsyncWebRequest
Reason: Perhaps sitemesh cannot set the response object from Spring MVC framework (AsynContext).
What is the reason ?
Please help.
Thanks.
Since the Sitemesh filter does some post-processing at the end of a request, it needs to support the Servlet 3 async request feature in order for this to work. When the initial Servlet container thread exits and the response remains open. If the Sitemesh filter is unaware of this, it will attempt to complete processing to early.
I am not an expect on sitemesh. But it's a servlet also so they follow the "chain of command" pattern which means it's possible it fail to transfer the correct url you need. can you post you config for async spring and sitemesh config in web.xml
It may be helpful. Return as a String instead of ModelAndView.
I have Spring MVC application where security is handled by Spring Security.
UI is built using GWT which gets the data from server using RPC approach.
I need to handle on UI the situation when session is expired:
For example RPC AsyncCallback can get SessionExpiredException type of exception and popup the window with message like "You session is expired, please click the refresh link" or something.
Did someone deal with such problem?
Thanks.
I suppose that for processing of incoming GWT call you use some Spring MVC controller or some servlet. It can have following logic
try{
// decode payload from GWT call
com.google.gwt.user.server.rpc.RPC.decodeRequest(...)
// get spring bean responsible for actual business logic
Object bean = applicationContext.getBean(beanName);
// execute business logic and encode response
return RPC.invokeAndEncodeResponse(bean, ….)
} catch (com.google.gwt.user.server.rpc.UnexpectedException ex) {
// send unexpected exception to client
return RPC.encodeResponseForFailure(..., new MyCustomUnexpectedException(), …) ;
}
Solution for this case
HttpServletRequest request = getRequest() ;
if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
return RPC.encodeResponseForFailure(..., new MyCustomSessionExpiredException(), …) ;
} else {
// first code snippet goes here
}
Then catch custom session expired exception in a client side code. If you do not use RPC directly then provide more details about your bridge implementation between GWT and Spring.
You will need also force GWT compiler to include MyCustomSessionExpiredException type to a serialization white list (to prevent case when GWT security policy stops propogation of the exception to client side). Solution: include MyCustomSessionExpiredException type to each method signature of each synchronous interface:
#RemoteServiceRelativePath("productRpcService.rpc")
public interface ProductRpcService extends RemoteService {
List<Product> getAllProducts() throws ApplicationException;
void removeProduct(Product product) throws ApplicationException;
}
MyCustomSessionExpiredException extends ApplicationException
Then show pop-up in client side code:
public class ApplicationUncaughtExceptionHandler implements GWT.UncaughtExceptionHandler {
#Override
public void onUncaughtException(Throwable caught) {
if (caught instanceof MyCustomSessionExpiredException) {
Window.alert("Session expired");
}
}
}
// Inside of EntryPoint.onModuleLoad method
GWT.setUncaughtExceptionHandler(new ApplicationUncaughtExceptionHandler());
I researched a bit and uploaded the solution here http://code.google.com/p/gspring/source/browse/#svn%2Ftrunk%2Fsample%2Fsession-expired%253Fstate%253Dclosed.
Use mvn jetty:run-war to see the demo after checking it out and go to rpc-security-sample/index.htm
There are two ways to solve it.
The first is around to pass the delegate proxy for GWT RemoteServlet which throws SessionExpiredException during method invocation. This requires to declare Exception in every RPC service method. Example: http://code.google.com/p/gspring/source/browse/#svn%2Ftrunk%2Fsample%2Fsession-expired%253Fstate%253Dclosed
Steps:
Develop new filter which intercepts first
Declare SessionExpiredException in each RPC method service which could inherit RuntimeException for simplicity (no need to follow this in implementers)
Develop parent generic AsyncCallback handler
Use http://code.google.com/p/gspring/ solution to handle all incoming RCP requests.
The second which is much more simplest: return the 401 HTTP error and handle in UI side (GWT native general exception contains the HTTP status number). Example: http://code.google.com/p/gspring/source/browse/#svn%2Ftrunk%2Fsample%2Fsession-expired-401
The second approach is simplest and does not require declaring Exception in service methods contract. However following the first approach can give you some flexibility: it could contain some additional info like last login time (for SessionExpiredException) etc. Also the second approach can introduce new exceptions which are inherited from SecurityException like blacklisted user (for example if user was blacklisted during his session) or for example if user does the same actions very often like a robot (it could be asked for passing the captcha) etc.