DEV Community

Dev Cookies
Dev Cookies

Posted on

🔄 Understanding Spring Boot Lifecycle Hooks: From Bean Creation to Application Ready

When building production-grade Spring Boot microservices, understanding the lifecycle of your application is crucial. Lifecycle hooks allow you to run initialization or cleanup tasks at precise moments—from bean creation to application shutdown. This ensures reliable initialization, safe shutdown, and better observability.

This blog covers all Spring Boot lifecycle hooks, their usage, timing, and best practices.


1️⃣ Bean-Level Lifecycle Hooks

Spring beans have their own lifecycle, and you can tap into it at various points.

a. @PostConstruct

  • Executes after the bean is instantiated and dependencies injected.
  • Ideal for lightweight initialization, e.g., setting default values or registering resources.
@Component
public class MyBean {
    @PostConstruct
    public void init() {
        System.out.println("Bean initialized: " + this);
    }
}
Enter fullscreen mode Exit fullscreen mode

Pros:

  • Simple, easy to use.
  • Ensures dependencies are injected.

Cons:

  • Should not run heavy tasks, as it blocks bean creation.
  • Runs before the application context is fully ready.

b. InitializingBean Interface

Alternative to @PostConstruct. Implement afterPropertiesSet():

@Component
public class MyBean implements InitializingBean {
    @Override
    public void afterPropertiesSet() {
        System.out.println("Bean ready after properties set");
    }
}
Enter fullscreen mode Exit fullscreen mode

Pros:

  • Standard Spring interface.
  • Useful when you want a programmatic approach instead of annotations.

c. Custom init-method in XML/Java Config

  • Specify a method to run after bean creation:
@Bean(initMethod = "customInit")
public MyBean myBean() { return new MyBean(); }
Enter fullscreen mode Exit fullscreen mode
  • Rarely used in modern Spring Boot projects due to annotations.

2️⃣ Application-Level Startup Hooks

These hooks run once the Spring context is ready, and are ideal for service-wide initialization.

a. CommandLineRunner

  • Runs after the Spring ApplicationContext is created.
  • Receives raw String[] args.
@Component
public class StartupTask implements CommandLineRunner {
    @Override
    public void run(String... args) {
        System.out.println("Application started, args: " + Arrays.toString(args));
    }
}
Enter fullscreen mode Exit fullscreen mode

Use cases:

  • Seed database.
  • Preload cache.
  • Validate external service availability.

b. ApplicationRunner

  • Similar to CommandLineRunner, but receives parsed ApplicationArguments.
@Component
public class StartupTask implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) {
        if (args.containsOption("warmup")) {
            preloadCache();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Pros:

  • Cleaner access to named arguments.

c. ApplicationListener<ApplicationReadyEvent>

  • Fires after the Spring context and embedded web server are ready.
  • Perfect for tasks that require the app to serve traffic.
@Component
public class ReadyListener {
    @EventListener(ApplicationReadyEvent.class)
    public void onReady() {
        System.out.println("Application fully started and ready");
    }
}
Enter fullscreen mode Exit fullscreen mode

Pros:

  • Guarantees the server is up.
  • Can be combined with metrics or readiness signaling.

3️⃣ Smart Lifecycle Hooks

a. SmartLifecycle Interface

  • Provides fine-grained control over start/stop phases.
  • Useful when multiple beans depend on each other.
@Component
public class MyLifecycleBean implements SmartLifecycle {
    private boolean running = false;

    @Override
    public void start() {
        System.out.println("Starting lifecycle bean");
        running = true;
    }

    @Override
    public void stop() { running = false; }

    @Override
    public boolean isRunning() { return running; }

    @Override
    public int getPhase() { return 0; } // ordering
    @Override
    public boolean isAutoStartup() { return true; }
}
Enter fullscreen mode Exit fullscreen mode

Use cases:

  • Integrating with external services that require a specific startup order.

4️⃣ Shutdown Hooks

Graceful shutdown is important in microservices and Kubernetes.

a. @PreDestroy

  • Executes before a bean is destroyed, e.g., closing connections.
@Component
public class CleanupBean {
    @PreDestroy
    public void cleanup() {
        System.out.println("Cleaning up resources...");
    }
}
Enter fullscreen mode Exit fullscreen mode

b. DisposableBean Interface

Alternative programmatic approach:

@Component
public class CleanupBean implements DisposableBean {
    @Override
    public void destroy() {
        System.out.println("Bean destroyed");
    }
}
Enter fullscreen mode Exit fullscreen mode

c. ApplicationListener<ContextClosedEvent>

  • Fires when the Spring ApplicationContext is closing.
@Component
public class ShutdownListener {
    @EventListener(ContextClosedEvent.class)
    public void onShutdown() {
        System.out.println("Application context closing...");
    }
}
Enter fullscreen mode Exit fullscreen mode

Pros:

  • Allows global cleanup or service deregistration.

5️⃣ Summary Table of Hooks

Hook Timing Use Case Notes
@PostConstruct After bean creation Bean-level init Lightweight only
InitializingBean.afterPropertiesSet() After bean creation Same as above Programmatic
CommandLineRunner.run() After context initialized App-wide startup tasks Access raw args
ApplicationRunner.run() After context initialized App-wide startup tasks Access structured args
ApplicationReadyEvent After server ready Tasks requiring full readiness Ideal for warmups, metrics
SmartLifecycle.start() Ordered lifecycle control Complex multi-bean init Specify phase/order
@PreDestroy Before bean destroyed Cleanup resources Lightweight only
DisposableBean.destroy() Before bean destroyed Programmatic cleanup Alternative to @PreDestroy
ContextClosedEvent When context closing Global cleanup Useful for deregistration

✅ Best Practices

  1. Separate lightweight and heavy tasks
  • Use @PostConstruct for bean-level initialization.
  • Use ApplicationReadyEvent or CommandLineRunner for heavy, app-wide tasks.
  1. Make startup tasks idempotent
  • Pods may restart in Kubernetes; tasks can run multiple times.
  1. Integrate with readiness probes
  • Signal that the app is ready only after critical tasks finish.
  1. Avoid blocking Spring Boot startup excessively
  • Use async threads if long-running tasks are not critical before serving traffic.
  1. Graceful shutdown
  • Always release resources and deregister services in shutdown hooks.

Understanding these lifecycle hooks allows you to control startup, runtime, and shutdown behavior precisely, which is crucial in production microservices environments.


Top comments (0)