Context module for the Micronaut Framework that extends the micronaut-inject module with additional bean container services such as job scheduling with @Scheduled, event listeners with @EventListener, and immutable configuration properties
—
Reactive event publishing and listening system for decoupled communication between application components. Supports strongly-typed events, automatic event listener registration, and comprehensive application lifecycle events.
Declarative event listener registration for automatic event handling.
/**
* Annotation for methods that should listen for application events
* Automatically registers the method as an ApplicationEventListener
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Adapter(ApplicationEventListener.class)
public @interface EventListener {
// Marker annotation - no attributes needed
// Event type is determined from method parameter type
}Usage Examples:
import io.micronaut.runtime.event.annotation.EventListener;
import io.micronaut.runtime.server.event.ServerStartupEvent;
import io.micronaut.runtime.server.event.ServerShutdownEvent;
import io.micronaut.context.event.StartupEvent;
import io.micronaut.context.event.ShutdownEvent;
import jakarta.inject.Singleton;
@Singleton
public class ApplicationEventListener {
@EventListener
public void onStartup(ServerStartupEvent event) {
EmbeddedServer server = event.getSource();
System.out.println("Server started on: " + server.getURL());
System.out.println("Application ready to handle requests");
}
@EventListener
public void onShutdown(ServerShutdownEvent event) {
System.out.println("Server shutting down");
// Cleanup resources, close connections, etc.
}
@EventListener
public void onApplicationStartup(StartupEvent event) {
ApplicationContext context = event.getSource();
System.out.println("Application context started");
}
@EventListener
public void onApplicationShutdown(ShutdownEvent event) {
System.out.println("Application context shutting down");
}
// Custom event listener
@EventListener
public void onCustomEvent(CustomBusinessEvent event) {
System.out.println("Business event: " + event.getData());
}
// Async event handling
@EventListener
@Async
public CompletableFuture<Void> onAsyncEvent(AsyncProcessingEvent event) {
return CompletableFuture.runAsync(() -> {
// Long-running event processing
processEventData(event.getData());
});
}
}Built-in events for tracking application and server lifecycle.
/**
* Base class for embedded application events
* Provides common functionality for application lifecycle events
*/
public abstract class AbstractEmbeddedApplicationEvent extends ApplicationEvent {
/**
* Create application event with embedded application source
* @param source The embedded application that generated the event
*/
public AbstractEmbeddedApplicationEvent(EmbeddedApplication<?> source);
/**
* Get the embedded application that generated this event
* @return EmbeddedApplication instance
*/
public EmbeddedApplication<?> getSource();
}
/**
* Event fired when EmbeddedApplication starts up
* Indicates the application context is ready and services are available
*/
public class ApplicationStartupEvent extends AbstractEmbeddedApplicationEvent {
/**
* Create startup event
* @param source The embedded application that started
*/
public ApplicationStartupEvent(EmbeddedApplication<?> source);
}
/**
* Event fired when EmbeddedApplication shuts down
* Last chance to perform cleanup before application termination
*/
public class ApplicationShutdownEvent extends AbstractEmbeddedApplicationEvent {
/**
* Create shutdown event
* @param source The embedded application that is shutting down
*/
public ApplicationShutdownEvent(EmbeddedApplication<?> source);
}Events specific to embedded server lifecycle for web applications.
/**
* Event fired when EmbeddedServer completes startup
* Server is ready to accept HTTP requests
*/
public class ServerStartupEvent extends ApplicationStartupEvent {
/**
* Create server startup event
* @param embeddedServer The server that started
*/
public ServerStartupEvent(EmbeddedServer embeddedServer);
/**
* Get the embedded server that started
* @return EmbeddedServer instance
*/
public EmbeddedServer getSource();
}
/**
* Event fired when EmbeddedServer shuts down
* Server will no longer accept new requests
*/
public class ServerShutdownEvent extends ApplicationEvent {
/**
* Create server shutdown event
* @param embeddedServer The server that is shutting down
*/
public ServerShutdownEvent(EmbeddedServer embeddedServer);
/**
* Get the embedded server that is shutting down
* @return EmbeddedServer instance
*/
public EmbeddedServer getSource();
}Usage Examples:
import io.micronaut.runtime.server.event.ServerStartupEvent;
import io.micronaut.runtime.server.event.ServerShutdownEvent;
import io.micronaut.runtime.event.annotation.EventListener;
@Singleton
public class ServerLifecycleListener {
@EventListener
public void onServerStartup(ServerStartupEvent event) {
EmbeddedServer server = event.getSource();
// Log server startup details
System.out.println("🚀 Server started successfully!");
System.out.println(" URL: " + server.getURL());
System.out.println(" Port: " + server.getPort());
System.out.println(" Host: " + server.getHost());
// Initialize external services
initializeExternalConnections();
// Register with service discovery
registerWithServiceRegistry(server);
}
@EventListener
public void onServerShutdown(ServerShutdownEvent event) {
System.out.println("🛑 Server shutting down...");
// Deregister from service discovery
deregisterFromServiceRegistry();
// Close external connections
closeExternalConnections();
System.out.println("✅ Graceful shutdown completed");
}
}Programmatic event publishing for custom business events.
/**
* ApplicationEventPublisher interface for publishing events
* Automatically available as an injectable bean
*/
public interface ApplicationEventPublisher<T> {
/**
* Publish an event to all registered listeners
* Executes synchronously, blocking until all listeners complete
* @param event The event to publish
*/
void publishEvent(@NonNull T event);
/**
* Publish an event asynchronously
* @param event The event to publish
* @return Future representing the async operation
*/
@NonNull Future<Void> publishEventAsync(@NonNull T event);
/**
* Check whether this publisher is empty (has no listeners)
* @return true if there are no subscribers
*/
boolean isEmpty();
/**
* Returns a no-op instance of ApplicationEventPublisher
* @param <K> The event type
* @return no-op ApplicationEventPublisher instance
*/
static <K> ApplicationEventPublisher<K> noOp();
}Usage Examples:
import io.micronaut.context.event.ApplicationEventPublisher;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
// Custom business event
public class OrderProcessedEvent {
private final String orderId;
private final BigDecimal amount;
private final Instant processedAt;
public OrderProcessedEvent(String orderId, BigDecimal amount) {
this.orderId = orderId;
this.amount = amount;
this.processedAt = Instant.now();
}
// Getters...
public String getOrderId() { return orderId; }
public BigDecimal getAmount() { return amount; }
public Instant getProcessedAt() { return processedAt; }
}
@Singleton
public class OrderService {
@Inject
private ApplicationEventPublisher<OrderProcessedEvent> eventPublisher;
public void processOrder(Order order) {
// Process the order
doOrderProcessing(order);
// Publish event synchronously
OrderProcessedEvent event = new OrderProcessedEvent(
order.getId(),
order.getTotal()
);
eventPublisher.publishEvent(event);
}
public CompletableFuture<Void> processOrderAsync(Order order) {
return CompletableFuture.runAsync(() -> {
doOrderProcessing(order);
}).thenCompose(v -> {
// Publish event asynchronously
OrderProcessedEvent event = new OrderProcessedEvent(
order.getId(),
order.getTotal()
);
return eventPublisher.publishEventAsync(event);
});
}
}
// Event listeners for the custom event
@Singleton
public class OrderEventHandlers {
@EventListener
public void onOrderProcessed(OrderProcessedEvent event) {
System.out.println("Order processed: " + event.getOrderId());
// Send confirmation email
sendConfirmationEmail(event.getOrderId());
}
@EventListener
@Async
public CompletableFuture<Void> updateAnalytics(OrderProcessedEvent event) {
return CompletableFuture.runAsync(() -> {
// Update business analytics asynchronously
analyticsService.recordOrderProcessed(event);
});
}
@EventListener
public void auditOrderProcessing(OrderProcessedEvent event) {
auditService.log("ORDER_PROCESSED", event.getOrderId(), event.getAmount());
}
}Event listeners with conditional execution based on application configuration.
/**
* Conditional event listener execution
* Use @Requires annotation to control when listeners are active
*/
import io.micronaut.context.annotation.Requires;
@Singleton
@Requires(property = "app.monitoring.enabled", value = "true")
public class MonitoringEventListener {
@EventListener
public void onServerStartup(ServerStartupEvent event) {
// Only active when monitoring is enabled
startupMonitor.recordStartup(event);
}
}
@Singleton
@Requires(env = "production")
public class ProductionEventListener {
@EventListener
public void onApplicationStartup(ApplicationStartupEvent event) {
// Only active in production environment
productionMetrics.recordStartup();
}
}Best practices for handling exceptions in event listeners.
@Singleton
public class RobustEventListener {
private static final Logger logger = LoggerFactory.getLogger(RobustEventListener.class);
@EventListener
public void onServerStartup(ServerStartupEvent event) {
try {
// Event processing logic
performStartupOperations(event);
} catch (Exception e) {
// Log error but don't propagate to prevent disrupting other listeners
logger.error("Error processing server startup event", e);
// Optional: publish error event for monitoring
errorEventPublisher.publishEvent(new EventProcessingError(e));
}
}
@EventListener
@Retryable(attempts = "3", delay = "1s")
public void onRetryableEvent(CustomEvent event) {
// This listener will be retried up to 3 times with 1-second delay
riskyOperation(event);
}
}Guidelines for creating custom event types.
/**
* Base pattern for custom business events
* Extend ApplicationEvent or create POJO with relevant data
*/
public class CustomBusinessEvent extends ApplicationEvent {
private final String businessData;
private final Instant timestamp;
public CustomBusinessEvent(Object source, String businessData) {
super(source);
this.businessData = businessData;
this.timestamp = Instant.now();
}
public String getBusinessData() { return businessData; }
public Instant getTimestamp() { return timestamp; }
}
// Alternative: Simple POJO event (recommended for most cases)
public class UserRegisteredEvent {
private final String userId;
private final String email;
private final Instant registeredAt;
public UserRegisteredEvent(String userId, String email) {
this.userId = userId;
this.email = email;
this.registeredAt = Instant.now();
}
// Getters...
}Complete Custom Event Example:
// Event definition
public class PaymentProcessedEvent {
private final String paymentId;
private final String customerId;
private final BigDecimal amount;
private final String currency;
private final PaymentStatus status;
private final Instant processedAt;
// Constructor and getters...
}
// Event publisher service
@Singleton
public class PaymentService {
@Inject
private ApplicationEventPublisher<PaymentProcessedEvent> eventPublisher;
public void processPayment(PaymentRequest request) {
// Process payment logic
PaymentResult result = processPaymentInternal(request);
// Publish event
PaymentProcessedEvent event = new PaymentProcessedEvent(
result.getPaymentId(),
request.getCustomerId(),
request.getAmount(),
request.getCurrency(),
result.getStatus()
);
eventPublisher.publishEvent(event);
}
}
// Multiple event listeners
@Singleton
public class PaymentEventHandlers {
@EventListener
public void onPaymentProcessed(PaymentProcessedEvent event) {
if (event.getStatus() == PaymentStatus.SUCCESS) {
// Send success notification
notificationService.sendPaymentConfirmation(event);
} else {
// Handle payment failure
alertService.notifyPaymentFailure(event);
}
}
@EventListener
@Async
public CompletableFuture<Void> updateCustomerBalance(PaymentProcessedEvent event) {
return CompletableFuture.runAsync(() -> {
customerService.updateBalance(event.getCustomerId(), event.getAmount());
});
}
@EventListener
public void auditPayment(PaymentProcessedEvent event) {
auditService.recordPayment(event);
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-micronaut--micronaut-context