skip to content

Which contexts can be refreshed more than once, and what are the close()/shutdown semantics?

level: principalimportance: nice to knowfreq 18%

answer

  1. Refreshable (XML/web) = new factory each refresh → reloadable
  2. Generic/AnnotationConfig = AtomicBoolean, single refresh, IllegalStateException
  3. close(): ContextClosedEvent → stop Lifecycle → destroySingletons → closeBeanFactory
  4. registerShutdownHook = close on JVM exit
  5. AutoCloseable → try-with-resources

basics

~10 s

AbstractRefreshableApplicationContext subclasses (like ClassPathXmlApplicationContext) can call refresh() repeatedly to reload definitions; GenericApplicationContext (and AnnotationConfigApplicationContext) can only refresh once. close() stops lifecycle beans and runs destroy callbacks; registerShutdownHook ties that to JVM exit.

solid answer

~30 s

There are two families. AbstractRefreshableApplicationContext (parent of ClassPathXmlApplicationContext, FileSystemXmlApplicationContext, and the classic XmlWebApplicationContext) discards its old DefaultListableBeanFactory and rebuilds definitions on each refresh() — so it supports multiple refreshes / config reload. GenericApplicationContext keeps a single fixed bean factory and guards refresh() with an AtomicBoolean, so a second call throws IllegalStateException; AnnotationConfigApplicationContext inherits that single-refresh limit. Shutdown is symmetric to startup: close() (ConfigurableApplicationContext is AutoCloseable) publishes ContextClosedEvent, calls lifecycleProcessor.onClose() to stop Lifecycle/SmartLifecycle beans in reverse phase order, then destroySingletons() runs @PreDestroy/DisposableBean callbacks and closes the factory. registerShutdownHook() registers a JVM hook so close runs on process exit.

code

java · 10 lines
java
// Refreshable: can reload definitions
var xml = new ClassPathXmlApplicationContext("beans.xml");
xml.refresh(); // rebuilds the bean factory from XML again — allowed

// Generic / annotation: single refresh only
var ac = new AnnotationConfigApplicationContext(AppConfig.class); // refresh() done in ctor
// ac.refresh(); // -> IllegalStateException (already refreshed)

ac.registerShutdownHook(); // close() (destroy callbacks) will run on JVM exit
ac.close();                // or explicit close via AutoCloseable

go deeper

for a junior

Know that close() shuts the context down and runs cleanup, and that some contexts refresh only once.

for a middle

Distinguish refreshable XML contexts from single-refresh Generic/annotation contexts and describe basic close() cleanup.

for a senior

Explain the AtomicBoolean single-refresh guard, the mirror-of-refresh close sequence, and registerShutdownHook / AutoCloseable.

for a principal

Reason about reload strategies (recreate vs re-refresh), reverse-order destruction of resources, devtools/restart mechanics, and graceful-shutdown implications in production.

**Two context families, two refresh policies.** 1. **`AbstractRefreshableApplicationContext`** — subclasses: `ClassPathXmlApplicationContext`, `FileSystemXmlApplicationContext`, classic `XmlWebApplicationContext`, `AnnotationConfigWebApplicationContext`. On every `refresh()` its `refreshBeanFactory()` **closes any existing `DefaultListableBeanFactory`, creates a brand-new one, and reloads all bean definitions** from the configured sources. This makes them *refreshable*: calling `refresh()` again reloads configuration (historically used for config hot-reload). Each refresh destroys the previous singletons first. 2. **`GenericApplicationContext`** — holds one `DefaultListableBeanFactory` for its entire life. `refreshBeanFactory()` only flips an internal `AtomicBoolean refreshed` from false→true; if it's already true it throws `IllegalStateException('GenericApplicationContext does not support multiple refresh attempts: just call refresh once')`. `AnnotationConfigApplicationContext` extends `GenericApplicationContext`, so it too is **single-refresh**. This is why annotation-based contexts are effectively immutable once started. **Practical consequence:** if you need reloadable configuration you must use a refreshable subclass; you cannot 'reload' an `AnnotationConfigApplicationContext` — you close it and build a new one. (Spring Boot's Actuator `RestartEndpoint`/devtools work by tearing down and recreating the context, not by re-refreshing it.) **close() / shutdown semantics** (`AbstractApplicationContext.close()` → `doClose()`), the mirror of refresh: - Sets `active=false`, `closed=true`. - Publishes **`ContextClosedEvent`** so listeners can react. - Calls `getLifecycleProcessor().onClose()` → **stops `Lifecycle`/`SmartLifecycle` beans in reverse phase order** (graceful shutdown; phased stop detail is a lifecycle-topic concern, here just note close triggers it). - Calls **`destroyBeans()` / `destroySingletons()`** which invokes destruction callbacks (`DisposableBean.destroy()`, `@PreDestroy`, custom `destroy-method`) on singletons in reverse creation order. - Calls `closeBeanFactory()` to release the factory. **JVM integration:** `registerShutdownHook()` adds a `Runtime.getRuntime().addShutdownHook(...)` that calls `close()` on JVM termination, so destroy callbacks fire even if you never explicitly close. Spring Boot registers this automatically. **AutoCloseable:** `ConfigurableApplicationContext extends Closeable/AutoCloseable`, so try-with-resources cleanly closes the context. **Gotchas:** (a) A closed context cannot be reused — `getBean` after close throws. (b) On a refreshable context, an exception during a *re-refresh* can leave you without a usable factory; the original is already discarded. (c) Idempotency: calling `close()` twice is safe (guarded by the `closed` flag); calling `refresh()` again on a Generic context is not. (d) Destruction order matters for resources (datasource pools, executors) — Spring destroys in reverse dependency order to avoid using an already-closed dependency.

  • Why can't you re-refresh an AnnotationConfigApplicationContext to reload beans?
    It extends GenericApplicationContext, which keeps one bean factory guarded by an AtomicBoolean; a second refresh() throws IllegalStateException. To reload you must close it and create a new context, which is how devtools/restart works.
  • What runs during close() and in what order relative to startup?
    close() is the mirror of refresh: publish ContextClosedEvent, stop Lifecycle/SmartLifecycle beans (reverse phase order), then destroySingletons() runs @PreDestroy/DisposableBean callbacks in reverse creation order, then the bean factory is closed.

saying these in an interview costs you the question

  • Claiming AnnotationConfigApplicationContext can be refreshed repeatedly
  • Saying close() is optional with no effect (it runs destroy callbacks and stops lifecycle beans)
  • Assuming destroy order is the same as creation order (it's reverse)
  • Believing a closed context can be reopened/reused

context