Simplified Email Notification Setup: Configuring Event Listeners in Java Applications

event-driven programming facilitates modular and scalable software design. By decoupling components through events and listeners, developers can create highly modular systems where individual components interact through well-defined interfaces, promoting code reusability and maintainability. This modular approach also enhances scalability, as new functionality can be added or modified with minimal impact on existing code. Additionally, event-driven architectures lend themselves well to distributed systems, where events can be used for communication between different services or modules, enabling the development of complex, distributed applications that can efficiently scale to meet changing demands. Overall, event-driven programming offers a powerful paradigm for building flexible, responsive, and scalable software systems across a wide range of domains.


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.

1. Project flow


terminal log showing a spring boot application startup




2. Types of implementation

there are various ways of implementing EventListener, two methods are popular

  1. by implementing ApplicationListener interface
  2. by an annotation @EventListener

  3. 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

3. By implementing ApplicationListener

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
          

3.1. Write an OrderController

      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;
          }
      }
          

3.2. Publishing an event

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;
          }
      }
          

3.3. Create a Listener

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);
          }
      }
          

3.5. Testing our application

go to your favorite browser and hit the url: http://localhost:9090/ you'll see an ice cream order form

an ice cream order form


select an ice cream flavour and hit place order

order placed successfully page


go to your inbox, you'll see a mail with subject 'Order Placed' is parked

mailbox


4. By an annotation @EventListener

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 ::: ");
          }
      }
          

4.1 Asynchronous Event By an annotation @EventListener

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);
          }
      }
          

4.2 Testing

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 :::
          

Conclusion

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.


source code

Get in touch

Let’s work together

Reach out if you have a concept for a website or mobile app or require guidance with product design. contact me.
  info@whywhytechnova.com
  +(91) 88661 28862