How does getPhase() control the order of startup and shutdown across SmartLifecycle beans?
answer
- ascending on start, descending on stop
- lower phase = starts first, stops last
- default phase = Integer.MAX_VALUE
- plain Lifecycle / non-Phased = phase 0
- same phase = no defined intra-order
basics
~20 sgetPhase() returns an int. On startup, lower phases start first; on shutdown the order reverses, so higher phases stop first and lower phases stop last. This lets low-level infrastructure start early and shut down last.
solid answer
~50 sSmartLifecycle extends Phased, whose getPhase() returns an int. DefaultLifecycleProcessor groups beans by phase and processes phases in **ascending** order at startup (lowest phase starts first) and **descending** order at shutdown (highest phase stops first). So a component with a lower phase starts earlier and is stopped later — ideal for foundational infrastructure (connection pools, registries) that everything else depends on. The default phase for SmartLifecycle is Integer.MAX_VALUE, meaning default beans start last and stop first, which is usually what you want for things like web servers or listener containers that should come up only after the rest is ready. Beans in the same phase are started together; within a shutdown phase the processor waits (up to a timeout) for all of them to finish before moving to the next phase. Plain Lifecycle beans and non-Phased beans are treated as phase 0.
code
java · 20 linesimport org.springframework.context.SmartLifecycle;
// Low phase: infrastructure. Starts first, stops last.
class ConnectionRegistry implements SmartLifecycle {
private boolean running;
@Override public int getPhase() { return -100; }
@Override public void start() { /* open pools */ running = true; }
@Override public void stop() { /* close pools */ running = false; }
@Override public boolean isRunning() { return running; }
}
// High (default) phase: message consumer. Starts last, stops first,
// so it drains before ConnectionRegistry closes its pools.
class OrderConsumer implements SmartLifecycle {
private boolean running;
// getPhase() not overridden -> Integer.MAX_VALUE (DEFAULT_PHASE)
@Override public void start() { /* begin consuming */ running = true; }
@Override public void stop() { /* stop consuming */ running = false; }
@Override public boolean isRunning() { return running; }
}go deeper
Just know lower phase starts first and shutdown reverses.
Should state the default is Integer.MAX_VALUE and explain the layered-infra rationale.
Should mention per-phase waiting on shutdown and that intra-phase order is undefined.
Designs phase numbering as a system-ordering contract and reasons about draining semantics relative to Boot's built-in lifecycle phases.
## Phases order the whole system's start and stop `SmartLifecycle` extends **`org.springframework.context.Phased`**, which declares `int getPhase()`. **`DefaultLifecycleProcessor`** uses this integer to order lifecycle transitions across *all* lifecycle beans in the context. ### The ordering rule - **Startup:** phases processed in **ascending** numeric order — the **lowest** phase starts **first**. - **Shutdown:** phases processed in **descending** order — the **highest** phase stops **first**, the lowest stops **last**. This symmetry means: *a bean with a lower phase starts before, and stops after, a bean with a higher phase.* That is exactly the semantics you want for layered infrastructure — the foundation (low phase) comes up first and is torn down last, while high-level consumers (high phase) come up last and are torn down first. ### The default phase and why it's MAX_VALUE `SmartLifecycle.DEFAULT_PHASE == Integer.MAX_VALUE`. So a SmartLifecycle bean that doesn't override `getPhase()` starts **last** and stops **first**. That's a sensible default for things like an embedded web server or a JMS/Kafka listener container: you want to begin accepting traffic/messages only after everything they depend on is already started, and you want to stop accepting *first* on shutdown so in-flight work can drain. ### Phase 0 and non-Phased beans A plain `Lifecycle` bean (not implementing `Phased`) is treated as phase **0**. So plain lifecycle beans sit between negative-phase infra and the MAX_VALUE default beans. ### Grouping and per-phase waiting Within a single phase, beans are started/stopped as a group. On **shutdown**, `DefaultLifecycleProcessor` stops all beans in the current (highest remaining) phase and then **waits for that phase's beans to report completion** before descending to the next phase. The wait is bounded by a per-phase timeout (default 30s; see the graceful-shutdown question). This ordered draining is why phases matter for correctness, not just aesthetics. ### Common phase conventions Spring Boot defines named constants, e.g. `SmartLifecycle.DEFAULT_PHASE` and Boot's `WebServerGracefulShutdownLifecycle.SMART_LIFECYCLE_TIMEOUT`-adjacent phases; Boot's graceful-shutdown lifecycle runs at a phase just below MAX_VALUE so the server stops accepting new requests before other MAX_VALUE beans stop. When you author your own, pick phases relative to the components you must order around: negative for early infra, small positive for mid-tier, leave app-level consumers at the default. ### Gotcha: equal phases have no defined intra-phase order Beans sharing a phase are not ordered relative to each other (no `@Order` guarantee inside a phase). If A must start strictly before B, give them different phases — don't rely on bean-definition order.
- Two SmartLifecycle beans return the same phase but one must start before the other. How do you guarantee it?You can't within one phase — intra-phase order is undefined. Assign different phases (a lower phase for the one that must start first). @Order/@DependsOn don't control lifecycle phase ordering.
- Why is the default phase Integer.MAX_VALUE rather than 0?So app-level consumers (servers, listeners) start after everything else and stop first, letting them drain before lower-phase infrastructure shuts down.