3.13 Context Events

Micronaut supports a general event system through the context. The ApplicationEventPublisher API publishes events and the ApplicationEventListener API is used to listen to events. The event system is not limited to events that Micronaut publishes and supports custom events created by users.

Publishing Events

The ApplicationEventPublisher API supports events of any type, although all events that Micronaut publishes extend ApplicationEvent.

To publish an event, use dependency injection to obtain an instance of ApplicationEventPublisher where the generic type is the type of event and invoke the publishEvent method with your event object.

Publishing an Event

  1. public class SampleEvent {
  2. private String message = "Something happened";
  3. public String getMessage() {
  4. return message;
  5. }
  6. public void setMessage(String message) {
  7. this.message = message;
  8. }
  9. }
  10. import io.micronaut.context.event.ApplicationEventPublisher;
  11. import jakarta.inject.Inject;
  12. import jakarta.inject.Singleton;
  13. @Singleton
  14. public class SampleEventEmitterBean {
  15. @Inject
  16. ApplicationEventPublisher<SampleEvent> eventPublisher;
  17. public void publishSampleEvent() {
  18. eventPublisher.publishEvent(new SampleEvent());
  19. }
  20. }

Publishing an Event

  1. class SampleEvent {
  2. String message = "Something happened"
  3. }
  4. import io.micronaut.context.event.ApplicationEventPublisher
  5. import jakarta.inject.Inject
  6. import jakarta.inject.Singleton
  7. @Singleton
  8. class SampleEventEmitterBean {
  9. @Inject
  10. ApplicationEventPublisher<SampleEvent> eventPublisher
  11. void publishSampleEvent() {
  12. eventPublisher.publishEvent(new SampleEvent())
  13. }
  14. }

Publishing an Event

  1. data class SampleEvent(val message: String = "Something happened")
  2. import io.micronaut.context.event.ApplicationEventPublisher
  3. import jakarta.inject.Inject
  4. import jakarta.inject.Singleton
  5. @Singleton
  6. class SampleEventEmitterBean {
  7. @Inject
  8. internal var eventPublisher: ApplicationEventPublisher<SampleEvent>? = null
  9. fun publishSampleEvent() {
  10. eventPublisher!!.publishEvent(SampleEvent())
  11. }
  12. }
Publishing an event is synchronous by default! The publishEvent method will not return until all listeners have been executed. Move this work off to a thread pool if it is time-intensive.

Listening for Events

To listen to an event, register a bean that implements ApplicationEventListener where the generic type is the type of event.

Listening for Events with ApplicationEventListener

  1. import io.micronaut.context.event.ApplicationEventListener;
  2. import io.micronaut.docs.context.events.SampleEvent;
  3. import jakarta.inject.Singleton;
  4. @Singleton
  5. public class SampleEventListener implements ApplicationEventListener<SampleEvent> {
  6. private int invocationCounter = 0;
  7. @Override
  8. public void onApplicationEvent(SampleEvent event) {
  9. invocationCounter++;
  10. }
  11. public int getInvocationCounter() {
  12. return invocationCounter;
  13. }
  14. }
  15. import io.micronaut.context.ApplicationContext;
  16. import io.micronaut.docs.context.events.SampleEventEmitterBean;
  17. import org.junit.Test;
  18. import static org.junit.Assert.assertEquals;
  19. public class SampleEventListenerSpec {
  20. @Test
  21. public void testEventListenerIsNotified() {
  22. try (ApplicationContext context = ApplicationContext.run()) {
  23. SampleEventEmitterBean emitter = context.getBean(SampleEventEmitterBean.class);
  24. SampleEventListener listener = context.getBean(SampleEventListener.class);
  25. assertEquals(0, listener.getInvocationCounter());
  26. emitter.publishSampleEvent();
  27. assertEquals(1, listener.getInvocationCounter());
  28. }
  29. }
  30. }

Listening for Events with ApplicationEventListener

  1. import io.micronaut.context.event.ApplicationEventListener
  2. import io.micronaut.docs.context.events.SampleEvent
  3. import jakarta.inject.Singleton
  4. @Singleton
  5. class SampleEventListener implements ApplicationEventListener<SampleEvent> {
  6. int invocationCounter = 0
  7. @Override
  8. void onApplicationEvent(SampleEvent event) {
  9. invocationCounter++
  10. }
  11. }
  12. import io.micronaut.context.ApplicationContext
  13. import io.micronaut.docs.context.events.SampleEventEmitterBean
  14. import spock.lang.Specification
  15. class SampleEventListenerSpec extends Specification {
  16. void "test event listener is notified"() {
  17. given:
  18. ApplicationContext context = ApplicationContext.run()
  19. SampleEventEmitterBean emitter = context.getBean(SampleEventEmitterBean)
  20. SampleEventListener listener = context.getBean(SampleEventListener)
  21. expect:
  22. listener.invocationCounter == 0
  23. when:
  24. emitter.publishSampleEvent()
  25. then:
  26. listener.invocationCounter == 1
  27. cleanup:
  28. context.close()
  29. }
  30. }

Listening for Events with ApplicationEventListener

  1. import io.micronaut.context.event.ApplicationEventListener
  2. import io.micronaut.docs.context.events.SampleEvent
  3. import jakarta.inject.Singleton
  4. @Singleton
  5. class SampleEventListener : ApplicationEventListener<SampleEvent> {
  6. var invocationCounter = 0
  7. override fun onApplicationEvent(event: SampleEvent) {
  8. invocationCounter++
  9. }
  10. }
  11. import io.kotest.matchers.shouldBe
  12. import io.kotest.core.spec.style.AnnotationSpec
  13. import io.micronaut.context.ApplicationContext
  14. import io.micronaut.docs.context.events.SampleEventEmitterBean
  15. class SampleEventListenerSpec : AnnotationSpec() {
  16. @Test
  17. fun testEventListenerWasNotified() {
  18. val context = ApplicationContext.run()
  19. val emitter = context.getBean(SampleEventEmitterBean::class.java)
  20. val listener = context.getBean(SampleEventListener::class.java)
  21. listener.invocationCounter.shouldBe(0)
  22. emitter.publishSampleEvent()
  23. listener.invocationCounter.shouldBe(1)
  24. context.close()
  25. }
  26. }
The supports method can be overridden to further clarify events to be processed.

Alternatively, use the @EventListener annotation if you do not wish to implement an interface or utilize one of the built-in events like StartupEvent and ShutdownEvent:

Listening for Events with @EventListener

  1. import io.micronaut.docs.context.events.SampleEvent;
  2. import io.micronaut.context.event.StartupEvent;
  3. import io.micronaut.context.event.ShutdownEvent;
  4. import io.micronaut.runtime.event.annotation.EventListener;
  5. @Singleton
  6. public class SampleEventListener {
  7. private int invocationCounter = 0;
  8. @EventListener
  9. public void onSampleEvent(SampleEvent event) {
  10. invocationCounter++;
  11. }
  12. @EventListener
  13. public void onStartupEvent(StartupEvent event) {
  14. // startup logic here
  15. }
  16. @EventListener
  17. public void onShutdownEvent(ShutdownEvent event) {
  18. // shutdown logic here
  19. }
  20. public int getInvocationCounter() {
  21. return invocationCounter;
  22. }
  23. }

Listening for Events with @EventListener

  1. import io.micronaut.docs.context.events.SampleEvent
  2. import io.micronaut.context.event.StartupEvent
  3. import io.micronaut.context.event.ShutdownEvent
  4. import io.micronaut.runtime.event.annotation.EventListener
  5. @Singleton
  6. class SampleEventListener {
  7. int invocationCounter = 0
  8. @EventListener
  9. void onSampleEvent(SampleEvent event) {
  10. invocationCounter++
  11. }
  12. @EventListener
  13. void onStartupEvent(StartupEvent event) {
  14. // startup logic here
  15. }
  16. @EventListener
  17. void onShutdownEvent(ShutdownEvent event) {
  18. // shutdown logic here
  19. }
  20. }

Listening for Events with @EventListener

  1. import io.micronaut.docs.context.events.SampleEvent
  2. import io.micronaut.context.event.StartupEvent
  3. import io.micronaut.context.event.ShutdownEvent
  4. import io.micronaut.runtime.event.annotation.EventListener
  5. @Singleton
  6. class SampleEventListener {
  7. var invocationCounter = 0
  8. @EventListener
  9. internal fun onSampleEvent(event: SampleEvent) {
  10. invocationCounter++
  11. }
  12. @EventListener
  13. internal fun onStartupEvent(event: StartupEvent) {
  14. // startup logic here
  15. }
  16. @EventListener
  17. internal fun onShutdownEvent(event: ShutdownEvent) {
  18. // shutdown logic here
  19. }
  20. }

If your listener performs work that might take a while, use the @Async annotation to run the operation on a separate thread:

Asynchronously listening for Events with @EventListener

  1. import io.micronaut.docs.context.events.SampleEvent;
  2. import io.micronaut.runtime.event.annotation.EventListener;
  3. import io.micronaut.scheduling.annotation.Async;
  4. @Singleton
  5. public class SampleEventListener {
  6. private AtomicInteger invocationCounter = new AtomicInteger(0);
  7. @EventListener
  8. @Async
  9. public void onSampleEvent(SampleEvent event) {
  10. invocationCounter.getAndIncrement();
  11. }
  12. public int getInvocationCounter() {
  13. return invocationCounter.get();
  14. }
  15. }
  16. import io.micronaut.context.ApplicationContext;
  17. import io.micronaut.docs.context.events.SampleEventEmitterBean;
  18. import org.junit.Test;
  19. import static java.util.concurrent.TimeUnit.SECONDS;
  20. import static org.awaitility.Awaitility.await;
  21. import static org.hamcrest.Matchers.equalTo;
  22. import static org.junit.Assert.assertEquals;
  23. public class SampleEventListenerSpec {
  24. @Test
  25. public void testEventListenerIsNotified() {
  26. try (ApplicationContext context = ApplicationContext.run()) {
  27. SampleEventEmitterBean emitter = context.getBean(SampleEventEmitterBean.class);
  28. SampleEventListener listener = context.getBean(SampleEventListener.class);
  29. assertEquals(0, listener.getInvocationCounter());
  30. emitter.publishSampleEvent();
  31. await().atMost(5, SECONDS).until(listener::getInvocationCounter, equalTo(1));
  32. }
  33. }
  34. }

Asynchronously listening for Events with @EventListener

  1. import io.micronaut.docs.context.events.SampleEvent
  2. import io.micronaut.runtime.event.annotation.EventListener
  3. import io.micronaut.scheduling.annotation.Async
  4. @Singleton
  5. class SampleEventListener {
  6. AtomicInteger invocationCounter = new AtomicInteger(0)
  7. @EventListener
  8. @Async
  9. void onSampleEvent(SampleEvent event) {
  10. invocationCounter.getAndIncrement()
  11. }
  12. }
  13. import io.micronaut.context.ApplicationContext
  14. import io.micronaut.docs.context.events.SampleEventEmitterBean
  15. import spock.lang.Specification
  16. import spock.util.concurrent.PollingConditions
  17. class SampleEventListenerSpec extends Specification {
  18. void "test event listener is notified"() {
  19. given:
  20. def context = ApplicationContext.run()
  21. def emitter = context.getBean(SampleEventEmitterBean)
  22. def listener = context.getBean(SampleEventListener)
  23. expect:
  24. listener.invocationCounter.get() == 0
  25. when:
  26. emitter.publishSampleEvent()
  27. then:
  28. new PollingConditions(timeout: 5).eventually {
  29. listener.invocationCounter.get() == 1
  30. }
  31. cleanup:
  32. context.close()
  33. }
  34. }

Asynchronously listening for Events with @EventListener

  1. import io.micronaut.docs.context.events.SampleEvent
  2. import io.micronaut.runtime.event.annotation.EventListener
  3. import io.micronaut.scheduling.annotation.Async
  4. import java.util.concurrent.atomic.AtomicInteger
  5. @Singleton
  6. open class SampleEventListener {
  7. var invocationCounter = AtomicInteger(0)
  8. @EventListener
  9. @Async
  10. open fun onSampleEvent(event: SampleEvent) {
  11. println("Incrementing invocation counter...")
  12. invocationCounter.getAndIncrement()
  13. }
  14. }
  15. import io.kotest.assertions.timing.eventually
  16. import io.kotest.matchers.shouldBe
  17. import io.kotest.core.spec.style.AnnotationSpec
  18. import io.micronaut.context.ApplicationContext
  19. import io.micronaut.docs.context.events.SampleEventEmitterBean
  20. import org.opentest4j.AssertionFailedError
  21. import kotlin.time.DurationUnit
  22. import kotlin.time.ExperimentalTime
  23. import kotlin.time.toDuration
  24. @ExperimentalTime
  25. class SampleEventListenerSpec : AnnotationSpec() {
  26. @Test
  27. suspend fun testEventListenerWasNotified() {
  28. val context = ApplicationContext.run()
  29. val emitter = context.getBean(SampleEventEmitterBean::class.java)
  30. val listener = context.getBean(SampleEventListener::class.java)
  31. listener.invocationCounter.get().shouldBe(0)
  32. emitter.publishSampleEvent()
  33. eventually(5.toDuration(DurationUnit.SECONDS), AssertionFailedError::class) {
  34. println("Current value of counter: " + listener.invocationCounter.get())
  35. listener.invocationCounter.get().shouldBe(1)
  36. }
  37. context.close()
  38. }
  39. }

The event listener by default runs on the scheduled executor. You can configure this thread pool as required in application.yml:

Configuring Scheduled Task Thread Pool

  1. micronaut:
  2. executors:
  3. scheduled:
  4. type: scheduled
  5. core-pool-size: 30