skip to content

What are the Spring Lifecycle and SmartLifecycle interfaces, and what do start(), stop() and isRunning() do?

level: juniorimportance: must knowfreq 55%

answer

  1. start / stop / isRunning
  2. plain Lifecycle NOT auto-started on refresh
  3. SmartLifecycle = auto-start + phase
  4. DefaultLifecycleProcessor drives it
  5. isRunning gates stop()

basics

~10 s

Lifecycle lets a bean receive start()/stop() callbacks tied to the container. isRunning() reports its state. SmartLifecycle extends it, adding auto-start on context refresh and a phase, so you rarely call start() yourself.

solid answer

~40 s

org.springframework.context.Lifecycle declares start(), stop(), and isRunning(). A bean implementing it can hook into the container's start/stop, but a plain Lifecycle bean is NOT started automatically on refresh — you must call ConfigurableApplicationContext.start(). SmartLifecycle extends Lifecycle (and Phased) and is the one you normally use: its isAutoStartup() defaults to true, so DefaultLifecycleProcessor starts it automatically at the end of context refresh and stops it on close. getPhase() controls ordering, and stop(Runnable) supports async shutdown. isRunning() must accurately reflect state because the processor only stops beans it believes are running. Typical uses: starting background pollers, message-listener containers, schedulers, or embedded servers that should run for the container's lifetime.

code

java · 30 lines
java
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;

@Component
class PollingService implements SmartLifecycle {
    private volatile boolean running = false;
    private Thread worker;

    @Override public void start() {
        worker = new Thread(this::poll, "poller");
        running = true;
        worker.start();
    }

    @Override public void stop() {
        running = false;               // signal the loop to exit
        if (worker != null) worker.interrupt();
    }

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

    // isAutoStartup() defaults to true -> started at end of refresh
    // getPhase() defaults to Integer.MAX_VALUE -> starts last, stops first

    private void poll() {
        while (running && !Thread.currentThread().isInterrupted()) {
            // ... do work ...
        }
    }
}

go deeper

for a junior

Know the three methods and that SmartLifecycle auto-starts; that's enough.

for a middle

Should explain the plain-Lifecycle-doesn't-auto-start gotcha and isAutoStartup default.

for a senior

Should connect it to DefaultLifecycleProcessor and phase-ordered start/stop.

for a principal

Frames Lifecycle as the container's ordered start/stop signal distinct from per-bean init, and reasons about isRunning() correctness under concurrency.

## The problem it solves `@PostConstruct`/`InitializingBean` fire per-bean during initialization, but they don't give you a container-wide, ordered *start the whole system now / stop it now* signal. Spring's **`org.springframework.context.Lifecycle`** interface fills that: it is a callback contract tied to the **application context's** own start and stop, not to individual bean construction. ### `Lifecycle` (the base interface) Three methods: - **`void start()`** — begin doing work (open connections, start threads, start listening). - **`void stop()`** — cease work and release resources. - **`boolean isRunning()`** — report current state. This is consulted by the container: on shutdown it only calls `stop()` on beans whose `isRunning()` returns `true`, and it won't re-`start()` a bean already running. **Critical gotcha:** a bean that implements *only* `Lifecycle` (not `SmartLifecycle`) is **not** auto-started when the context refreshes. `DefaultLifecycleProcessor.onRefresh()` starts only auto-startup beans, and a plain `Lifecycle` bean has no `isAutoStartup()`. Such a bean starts only when someone explicitly calls `ConfigurableApplicationContext.start()`. It *is* stopped on `close()`, though. This asymmetry surprises people — they implement `Lifecycle`, `start()` never runs, and they conclude Spring is broken. ### `SmartLifecycle` (what you almost always want) `SmartLifecycle extends Lifecycle, Phased` and adds: - **`boolean isAutoStartup()`** — default `true`. When true, `DefaultLifecycleProcessor` calls `start()` automatically at the end of context refresh (`finishRefresh()`), so you don't call `start()` yourself. - **`int getPhase()`** — from `Phased`; default is `SmartLifecycle.DEFAULT_PHASE == Integer.MAX_VALUE`. Startup runs in **ascending** phase order (lowest first); shutdown runs in **descending** order (highest first). So default SmartLifecycle beans start last and stop first. - **`void stop(Runnable callback)`** — supports asynchronous shutdown. The default implementation calls `stop()` then `callback.run()`; override it to defer `callback.run()` until your async cleanup completes. The processor waits per phase up to a timeout. ### Who drives it: `LifecycleProcessor` The context looks up a bean named `lifecycleProcessor` (a `LifecycleProcessor`), defaulting to **`DefaultLifecycleProcessor`**. `onRefresh()` starts auto-startup beans; `onClose()` stops all lifecycle beans in reverse phase order. `ConfigurableApplicationContext.start()/stop()` delegate to it too. ### When to use - Background pollers/schedulers that should live for the app's lifetime. - Message-listener containers (Spring's `AbstractMessageListenerContainer` is itself a `SmartLifecycle`). - Embedded servers (Spring Boot's web server is managed by a `SmartLifecycle`). - Anything needing *ordered* startup/shutdown relative to other components. ### When NOT to use One-shot per-bean init/cleanup belongs in `@PostConstruct`/`@PreDestroy`. Lifecycle is for start/stop of ongoing activity, potentially repeatable and phase-ordered.

  • If a bean implements only Lifecycle (not SmartLifecycle), does its start() run when the app boots?
    No. DefaultLifecycleProcessor.onRefresh() only starts auto-startup beans, and a plain Lifecycle bean isn't one. It starts only if you call ConfigurableApplicationContext.start() explicitly. It is still stopped on close().
  • Why must isRunning() be accurate?
    The processor uses it to avoid double-starting and to decide whether to call stop() — it only stops beans it believes are running. A stale value can skip your cleanup or throw during shutdown.

context