I've seen many a posts and Q&As on StackOverflow for custom error handling for a REST/MVC applications.
My situation is slightly different though. My application uses Java Messaging (with ActiveMQ and SpringIntegration) and
the method below is triggered as a response to a JMessage:
public BenefitVersion getVersionAt(LocalDate referenceDate) {
return versions.stream()
.filter(benefitVersion -> !benefitVersion.getStartDate().isAfter(referenceDate))
.reduce(maxBy(getBenefitVersionStartDateComparator()
.thenComparingLong(BenefitVersion::getId)))
.orElseThrow(() -> new IllegalBenefitVersionException(guid,
format("Benefit %s does not have active version on %s", guid, referenceDate)));
}
I've defined custom exception as below:
public class IllegalBenefitVersionException extends RuntimeException {
private UUID benefitId;
public IllegalBenefitVersionException(final UUID benefitId, final String message) {
super(message);
this.benefitId = benefitId;
}
}
And a custom handler for that exception:
#ControllerAdvice
public class IllegalBenefitVersionExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(IllegalBenefitVersionExceptionHandler.class);
private final DlqExceptionDetailService exceptionDetailService;
#Autowired
public IllegalBenefitVersionExceptionHandler(DlqExceptionDetailService exceptionDetailService) {
this.exceptionDetailService = exceptionDetailService;
}
#ExceptionHandler(IllegalBenefitVersionException.class)
public void handleException(Throwable t) {
IllegalBenefitVersionException exception = (IllegalBenefitVersionException) t;
exceptionDetailService.newWithBenefitIdAndSave(exception.getBenefitId(), t.getMessage());
LOGGER.error("From ExceptionHandler: {}", t.getMessage(), t);
}
}
But that never gets called. Is the reason because I use the #ControllerAdvice whereas there is no controller?
Or perhaps I need to add special component scan somewhere?
If so, how do I wire up a custom exception handler in a messaging-based, as opposed to, REST, application?
Related
I have some validation code that should run on server startup and make sure various conditions are met so whoever deploys the server don't messes up the DB or start the server with bad security configurations etc. To do that I created a bean
#Component
public class ApplicationStartupConditionsValidationBean {
static class ServerInitializationError extends Error{
public ServerInitializationError(String msg){
super(msg);
}
}
private Environment springEnv;
private String datasourceURL;
private String ddlAuto;
private static final Logger logger = LogManager.getLogger();
#Autowired
public ApplicationStartupConditionsValidationBean(Environment springEnv) throws Exception {
this.springEnv = springEnv;
this.datasourceURL = springEnv.getProperty("spring.datasource.url");
this.ddlAuto = springEnv.getProperty("spring.jpa.hibernate.ddl-auto");
validateStartupConditions();
}
public boolean isDBLocal(){
return datasourceURL.startsWith("jdbc:postgresql://localhost:");
}
private String disallowedParamMsg(String optionName, String optionValue){
return "option " + optionName + "=" + optionValue + " not allowed in production";
}
private void reject(String msg) throws ServerInitializationError{
String rejectionMsg = "startup conditions validation failed with msg: " + msg;
logger.error(rejectionMsg);
throw new ServerInitializationError(rejectionMsg);
}
private void reject(String paramName, String paramValue) throws ServerInitializationError{
reject(disallowedParamMsg(paramName, paramValue));
}
private void validateDatasourceParams(){
if(!isDBLocal() &&
!ddlAuto.equals("validate")){
reject("ddl-auto", ddlAuto);
}
}
public void validateStartupConditions() throws Exception{
logger.info("validating startup conditions");
validateDatasourceParams();
// more validation logic...
logger.info("startup conditions validation succeeded, proceeding with boot");
}
}
The way I would have wanted to use this class is to define what beans this must come before. In the example here I would have wanted to make sure this bean would be created before the DataSource bean is created, so that "ddl-auto=create" doesn't slip in production. I Know about the #DependsOn annotation but I would have wanted to do the reverse, and declare that this bean #HappensBefore a list of other beans. Is there any way to do this?
Thanks!
To run code before "normal" beans get created, you can use a BeanFactoryPostProcessor.
To declaratively add additional dependencies among beans, you could also use a BeanPostProcessor, but that sounds needlessly cumbersome for your use case.
I have a notification service(I have control of this class).
If there is any unchecked exception, then I do not want to throw it. But instead want to log it in a specific manner.
I can directly have try catch block in each method's implementation but I am in search of some magic here 😄
Is there a common way that this can be handled through Spring?
Update:
AOP is also a way to do it. For example: https://dzone.com/articles/handling-exceptions-using-springs-aop
Any other direct implementation for this?
This was my requirement but I was not able to find anything with respect to this.
I faced similar issues when dealing with calling multiple apis from rest service, where i was suppose to provide a fallback implementation when error occured. My Aspect was more than what i am giving example here.
Service
#Service
public class SuspiciousService {
final Random random = new Random();
public String beSuspicious() {
final boolean value = random.nextBoolean();
if (value) {
throw new IllegalStateException("Exception occured for: " + value);
}
return "I am not suspicious";
}
}
Sample service which randomly throws an error.
Controller
#RestController
#RequestMapping("/is-suspicious")
#AllArgsConstructor
public class SampleController {
private final SuspiciousService suspiciousService;
#GetMapping
public Map<String, String> get() {
return Map.of("isSuspicious", suspiciousService.beSuspicious());
}
}
Controller which invokes this service.
Finally, Around Aspect catches this exception and provides the sample response.
#Aspect
#Component
#Order(2)
public class AspectAroundSuspiciousService {
#Around("execution(* in.silentsudo.sprintbootresttemplate.SuspiciousService.beSuspicious(..))")
public Object parallelExecuteBeforeAndAfterCompose(ProceedingJoinPoint point) throws Throwable {
try {
return point.proceed();
} catch (RuntimeException re) {
return "Yes, I am suspicious";
}
}
}
The other approach is if you are using ByteBuddy, you can add annotation to the method throwing exception
#Advice.OnMethodExit(onThrowable = RuntimeException.class)
and have an ExceptionHandler to cath this
#ExceptionHandler
private String suspiciousRuntimeException(RuntimeException exception) {
return "Yes, I am suspicious from ex handler, error: " + exception.getMessage();
}
I choose aspect over bytebuddy for simple reason as i was handling ladder of api exception, where as this implementation will catch in general RuntimeException happenning from service#method
After upgrading the micronaut application from 2.5.12 to 3.0.0 and using the Project reactor as reactive stream. The Global exception handler method never get called.
public class GlobalException extends RuntimeException{
public GlobalException(Throwable throwable){super(throwable);}
}
#Produces
#Singleton
#Requires(classes = {GlobalException.class, ExceptionHandler.class})
public class GlobalExceptionHandler implements ExceptionHandler<GlobalException, HttpResponse> {
private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionHandler.class);
#Override
public HttpResponse handle(HttpRequest request, GlobalException exception) {
LOG.error(exception.getLocalizedMessage());
LOG.error(exception.getCause().getMessage());
Arrays.stream(exception.getStackTrace()).forEach(item -> LOG.error(item.toString()));
return HttpResponse.serverError(exception.getLocalizedMessage());
}
}
On exception with below code, the handler method never get called
#Override
public Flux<FindProductCommand> get(ProductSearchCriteriaCommand searchCriteria) {
LOG.info("Controller --> Finding all the products");
return iProductManager.find(searchCriteria).onErrorMap(throwable -> {
return new GlobalException(throwable);
});
}
I had this code Global exception handling in micronaut Java from rxjava 3 which was working fine, however, now with project reactor it is not working
If you only intend to produce a single element it should be annotated with #SingleResult Or use Mono
#Override
#SingleResult
public Flux<List<FindProductCommand>> freeTextSearch(String text) {
LOG.info("Controller --> Finding all the products");
return iProductManager.findFreeText(text)
.onErrorMap(throwable -> {
throw new GlobalException(throwable);
});
}
Try this one:
#Override
public Flux<FindProductCommand> get(ProductSearchCriteriaCommand searchCriteria) {
LOG.info("Controller --> Finding all the products");
return iProductManager
.find(searchCriteria)
.onErrorResume(e -> Mono.error(new GlobalException("My exception", e));
}
One reason for this might be that in your code, looking at this bit:
#Override
public Flux<FindProductCommand> get(ProductSearchCriteriaCommand searchCriteria) {
LOG.info("Controller --> Finding all the products");
return iProductManager.find(searchCriteria).onErrorMap(throwable -> {
return new GlobalException(throwable);
});
}
If the code iProductManager.find(searchCriteria) is calling a rest endpoint and getting a 404 not found, you will not get an error in the error handler. Rather the result is that you will get the equivalent of an Optional.empty().
If you want to force en error, you could change it to something like:
iProductManager.find(searchCriteria).switchIfEmpty(Mono.error(...))
I am trying to add a ErrorHandler via the EventProcessingConfigurer.registerErrorHandler() method and while it is showing on the configuration the class itself is not being called.
Am currently using Axon 4.1.1 (With out Axon server) and Spring Boot 2.1.6.RELEASE.
i have based my code off github/AxonFramework but it isn't acting the same.
Config:
#Autowired
public void configure(final EventProcessingConfigurer config) {
TestErrorHandler testErrorHandler = new TestErrorHandler();
config.registerErrorHandler("SolrProjection", configuration -> testErrorHandler);
}
ErrorHander:
public class TestErrorHandler implements ErrorHandler, ListenerInvocationErrorHandler {
#Override
public void handleError(final ErrorContext errorContext) throws Exception {
System.out.println("TestErrorHandler.handleError()");
}
#Override
public void onError(final Exception exception, final EventMessage<?> event, final EventMessageHandler eventHandler) {
System.out.println("TestErrorHandler.onError()");
}
}
Projection:
#Configuration
#RequiredArgsConstructor
#ProcessingGroup("SolrProjection")
public class SolrProjection {
#EventHandler
public void onEvent(final TestEvent event,
#SequenceNumber Long sequenceNumber,
#Timestamp final Instant requestTimestamp,
#MessageIdentifier final String messageIdentifier,
final MetaData metaData) {
if (true) {
throw new IllegalStateException();
}
}
even thou i am directly throwing an error, i do not ever see the two system.out's in console. and putting log statements in the #EventHandler are properly being called.
The ErrorHandler is tasked to dealing with different exceptions than what you expect.
When it comes to handling events, Axon Framework deduces two layers:
The internal EventProcessor layer
The Event Handling Components written by framework users
Exceptions thrown within the EventProcessor are dealt with by the ErrorHandler you've configured.
For customizing the process for handling exceptions from your own Event Handlers, you
will have to configure the ListenerInvocationErrorHandler.
To configure a general/default ListenerInvocationErrorHandler, you can use the following method in your first snippet:
EventProcessingConfigurer#registerDefaultListenerInvocationErrorHandler(
Function<Configuration, ListenerInvocationErrorHandler>
)
You can also check out Axon's Reference Guide at this page for more info on this.
Hope this helps you out #sherring!
I am using Spring Security 3.2.5.RELEASE and am having difficulty catching failed login attempts. I currently have an ApplicationListener configured as follows:
#Component
public class MyApplicationListener implements ApplicationListener<AbstractAuthenticationEvent> {
private static Logger logger = LogManager.getLogger(MyApplicationListener);
#Override
public void onApplicationEvent(AbstractAuthenticationEvent abstractAuthenticationEvent) {
if (abstractAuthenticationEvent instanceof AbstractAuthenticationFailureEvent) {
logger.warn("Detected an invalid login");
} else if (abstractAuthenticationEvent instanceof AuthenticationSuccessEvent) {
logger.info("A user logged in successfully");
}
}
}
If a user logs in successfully I can see the success message as expected. However, if a user supplies a bad password etc. the failure message never shows.
I changed the listener so it logs all ApplicationEvent as below:
#Component
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
private static Logger logger = LogManager.getLogger(MyApplicationListener);
#Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
logger.info("Got an event of type {}", applicationEvent.getClass());
}
}
However the only security-related events that are logged are of type org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent. A failed login does not trigger any events. I would appreciate any pointers, thanks.
Maybe is a bit late, but i got the same problem and solve it this way:
In your configureGlobal method:
auth.authenticationEventPublisher(defaultAuthenticationEventPublisher());
Dont forget to declare the DefaultAuthenticationEventPublisher as a Bean
#Bean
public DefaultAuthenticationEventPublisher defaultAuthenticationEventPublisher(){
return new DefaultAuthenticationEventPublisher();
}
More info here:
Why the event AbstractAuthenticationFailureEvent is never triggered in spring security?