How to manage the lifecycle of a Quarkus application

Quarkus 3.x uses Jakarta EE CDI events (@Observes StartupEvent and ShutdownEvent) to execute custom logic during application initialization and shutdown. With full support for Jakarta EE 10 and reactive stacks, managing application lifecycles and active profiles is clean, lightweight, and non-blocking.

CDI Events allow beans to communicate so that one bean can define an event, another bean can fire the event, and yet another bean can handle the event. Let’s see how we can take advantage of this to manage the lifecycle of a Quarkus 3.x application using modern Jakarta EE standards.

Start by creating a basic Quarkus project using the latest Quarkus 3.x Maven plugin:

mvn io.quarkus.platform:quarkus-maven-plugin:3.15.0:create \
    -DprojectGroupId=com.sample \
    -DprojectArtifactId=lifecycle-demo \
    -DclassName="com.sample.ExampleResource" \
    -Dpath="/hello"

Then, add the following Jakarta-compliant CDI Bean to your application:

import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;
import io.quarkus.runtime.configuration.ProfileManager;
import org.jboss.logging.Logger;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;

@ApplicationScoped
class ApplicationLifeCycle {

    private static final Logger LOGGER = Logger.getLogger(ApplicationLifeCycle.class);

    void onStart(@Observes StartupEvent ev) {
        LOGGER.info("The application has started");
    }

    void onStop(@Observes ShutdownEvent ev) {
        LOGGER.info("The application is stopping...");
    }
}

This is a classic example of using the CDI event mechanism to decouple event producers from observers. Quarkus runtime utilizes the jakarta.enterprise.event.Event class to create lifecycle events, while you use the CDI @Observes annotation to subscribe to them.

In our case, CDI @Observes the io.quarkus.runtime.StartupEvent and io.quarkus.runtime.ShutdownEvent lifecycle notifications. Therefore:

  • On application boot, the StartupEvent allows you to trigger initialization code before web endpoints start serving requests (including Quarkus REST / RESTEasy Reactive routes).
  • On termination, the ShutdownEvent lets you gracefully release resources, close reactive connections, or finalize tasks.

Run the application in Quarkus development mode:

mvn quarkus:dev

You will see the log output printed on your console:

[com.sam.ApplicationLifeCycle] (main) The application has started

If you stop the application using CTRL+C, the shutdown handler will execute:

[com.sam.ApplicationLifeCycle] (Quarkus Shutdown Thread) The application is stopping...

Interestingly enough, you can perform a different set of actions in your LifeCycle class based on the current active Profile.

By default, Quarkus provides three build-in profiles (though you can easily add custom ones):

  • dev – Activated when in development mode (i.e. quarkus:dev)
  • test – Activated when running integration and unit tests
  • prod – The default production profile when packaged and executed as a JAR or native binary

Let's update our startup method to log the active runtime profile:

void onStart(@Observes StartupEvent ev) {
    LOGGER.infof("The application is starting with profile `%s`", ProfileManager.getActiveProfile());
}

Now, when launching the application in development mode, Quarkus prints:

[com.sam.ApplicationLifeCycle] (main) The application is starting with profile `dev`

Frequently Asked Questions (FAQs)

1. How do I perform non-blocking or reactive tasks during application startup in Quarkus 3?

While standard StartupEvent observers run synchronously during startup, you can inject reactive services (such as Mutiny Uni or Multi pipeline callers or database clients like Reactive PostgreSQL Client) directly into your @ApplicationScoped lifecycle bean and invoke them asynchronously or await their completion using uni.await().indefinitely() during startup.

2. What is the difference between @Observes StartupEvent and the @Startup annotation?

The @Observes StartupEvent allows a specific method to execute code when the runtime fires the startup event. The @Startup annotation, placed at the class level on an @ApplicationScoped bean, forces Quarkus to eagerly initialize that bean at boot time rather than lazily when first requested by another service.

3. How can I control the ordering of multiple startup observer methods?

You can use the Jakarta EE @Priority annotation on your observer method. Observers with lower priority numerical values are executed first (e.g., @Priority(1) runs before @Priority(10)).


Recommended Articles

A Comprehensive Comparison of WildFly Application Server and Quarkus Framework in Enterprise Java

Explore the features and use cases of WildFly and Quarkus for robust Java applications. #WildFly #Quarkus #EnterpriseJava

Architecting MapStruct with CDI and Quarkus: Getting componentModel Right for Jakarta EE

Master MapStruct with CDI and Quarkus. Configure the right componentModel for Jakarta EE to build reflection-free, lightning-fast GraalVM native binaries.

Configure Default Transaction Timeout in Quarkus - A Comprehensive Guide

Learn how to configure and manage default transaction timeouts in Quarkus applications. #Quarkus #Java #Middleware

Optimizing Your Quarkus Application with Custom Undertow Server Settings

Learn how to customize your Quarkus application's embedded Undertow server settings. #Quarkus #Java #Middleware #CloudNative