In modern software development, keeping users informed about system events is crucial for providing a seamless user experience. Email notifications remain a widely used method for alerting users about important updates, reminders, or any other relevant information. In Java applications, configuring an event listener for email notifications can enhance user engagement and satisfaction. In this blog post, we'll explore how to set up an event listener for email notifications in a Java application.
there are various ways of implementing EventListener, two methods are popular
3rd approach is an asynchronous approach that is achieved by @EventListener
in this blog we'll do hands-on on all 3 methods of implementing EventListener in Java
include following dependencies in your spring boot project
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
include following properties
spring.mail.host=<host> spring.mail.port=<port> spring.mail.username=<username> spring.mail.password=<password> spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true
package com.example.eventlistenerdemo.controller; import com.example.eventlistenerdemo.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; /** * @author viveksoni */ @Controller public class OrderController { @Autowired private OrderService orderService; @GetMapping("/") public ModelAndView iceCreamOrderForm() { ModelAndView model = new ModelAndView("order-page"); model.addObject("orderplaced", Boolean.FALSE); return model; } @PostMapping("/placeorder") public ModelAndView placeAnOrder(@RequestParam("iceCreamFlavor") String iceCreamFlavor) { ModelAndView model = new ModelAndView("order-page"); orderService.placeOrder(iceCreamFlavor); model.addObject("orderplaced", Boolean.TRUE); return model; } }
we need to publish an event when user places an order for an ice-cream
package com.example.eventlistenerdemo.service; import com.example.eventlistenerdemo.event.OrderPlacedEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.stereotype.Service; /** * @author viveksoni */ @Service public class OrderService implements ApplicationEventPublisherAware { private ApplicationEventPublisher applicationEventPublisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } public void placeOrder(String iceCreamFlavor) { this.applicationEventPublisher.publishEvent(new OrderPlacedEvent(this, iceCreamFlavor)); } }
we also need to make our custom event, OrderPlaceEvent a spring Event, we can do that by simply extending an ApplicationEvent class
package com.example.eventlistenerdemo.event; import com.example.eventlistenerdemo.service.OrderService; import lombok.Getter; import org.springframework.context.ApplicationEvent; /** * @author viveksoni */ @Getter public class OrderPlacedEvent extends ApplicationEvent { private static final long serialVersionUID = 1L; private String iceCreamFlavor; public OrderPlacedEvent(OrderService orderService, String iceCreamFlavor) { super(orderService); this.iceCreamFlavor = iceCreamFlavor; } }
This is a very important step, here we define the tasks that we want to accomplish on a particular event, in our case we need to send an email to the user saying that his or her order for an ice cream is placed. You might want to send an SMS, or you might want to send a WhatsApp to the user, it could be anything.
package com.example.eventlistenerdemo.event.listener; import com.example.eventlistenerdemo.event.OrderPlacedEvent; import com.example.eventlistenerdemo.service.EmailService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** * @author viveksoni */ @Slf4j @Component @Order(1) // sets the order among multiple listener, 1 will execute first public class OrderPlacedEventListener implements ApplicationListener<OrderPlacedEvent> { @Autowired private EmailService emailService; @Override public void onApplicationEvent(OrderPlacedEvent event) { log.info("@Order(1) order placed for ice cream : {}", event.getIceCreamFlavor()); log.info("sending an email ::: "); emailService.sendSimpleMessage("recipient@yopmail.com", "Order Placed", "we've successfully placed your ice cream ::: " + event.getIceCreamFlavor()); log.info("an email sent ::: "); } }
here you might think, what is this @Order(1)? well you can totally omit this if you want to, however i've taken this, because it is very useful when you've applied multiple listener on an event, suppose we've implemented your listener using all 3 methods that i've mention on 2. Types of implementation.
for sending an email i've used Spring's JavaMailSender class, you are free to use you're favorite method of shooting an email.
package com.example.eventlistenerdemo.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; /** * @author viveksoni */ @Service public class EmailService { @Autowired private JavaMailSender emailSender; public void sendSimpleMessage(String to, String subject, String text) { SimpleMailMessage message = new SimpleMailMessage(); message.setTo(to); message.setSubject(subject); message.setText(text); emailSender.send(message); } }
go to your favorite browser and hit the url: http://localhost:9090/ you'll see an ice cream order form
select an ice cream flavour and hit place order
go to your inbox, you'll see a mail with subject 'Order Placed' is parked
in place of implementing ApplicationListener interface we will be annoting our method with @EventListener annotation
package com.example.eventlistenerdemo.event.listener; import com.example.eventlistenerdemo.event.OrderPlacedEvent; import com.example.eventlistenerdemo.service.EmailService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** * @author viveksoni */ @Slf4j @Component public class OrderPlacedEventListenerAnnotation { @Autowired private EmailService emailService; @EventListener @Order(2) public void handleEvent(OrderPlacedEvent event) { log.info("@Order(2) order placed for ice cream using @EventListener : {}", event.getIceCreamFlavor()); log.info("sending an email ::: "); emailService.sendSimpleMessage("recipient@yopmail.com", "Order Placed", "we've successfully placed your ice cream ::: " + event.getIceCreamFlavor()); log.info("an email sent ::: "); } }
to make our event asynchronous we'll add @Async annotation on our @EventListener annoted method
package com.example.eventlistenerdemo.event.listener; import com.example.eventlistenerdemo.event.OrderPlacedEvent; import com.example.eventlistenerdemo.service.EmailService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; /** * @author viveksoni */ @Slf4j @Component public class OrderPlacedEventListenerAsync { @Autowired private EmailService emailService; @Async @EventListener @Order(3) public void handleEvent(OrderPlacedEvent event) { log.info("@Order(3) order placed for ice cream using @EventListener and @Async : {}", event.getIceCreamFlavor()); log.info("sending an email ::: "); emailService.sendSimpleMessage("recipient@yopmail.com", "Order Placed", "we've successfully placed your ice cream ::: " + event.getIceCreamFlavor()); log.info("an email sent ::: "); } }
we also need to enabling asynchronous calls on our application level, we can do that by adding an annotation @EnableAsync on our springboot bootstrap class
package com.example.eventlistenerdemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @EnableAsync @EnableWebMvc @SpringBootApplication public class EventListenerDemoApplication { public static void main(String[] args) { SpringApplication.run(EventListenerDemoApplication.class, args); } }
again after placing an order for an ice cream, you'll notice that, it is taking a bit longer time than before, it is because we have configred 3 event listeners on a single event, also you'll see following logs printed on your console
: @Order(1) order placed for ice cream : americanNuts : sending an email ::: : an email sent ::: : @Order(2) order placed for ice cream using @EventListener : americanNuts : sending an email ::: : an email sent ::: : @Order(3) order placed for ice cream using @EventListener and @Async : americanNuts : sending an email ::: : an email sent :::
Event-driven programming is crucial in modern software development due to its ability to efficiently handle asynchronous and concurrent tasks. By structuring programs around events and event handlers, developers can create systems that respond dynamically to user interactions, system events, or external inputs. This paradigm is especially valuable in user interface development, where responsiveness and interactivity are paramount. Instead of relying on traditional procedural or sequential approaches, event-driven programming allows applications to remain responsive while waiting for user actions, ensuring a smoother user experience.