skip to content

Explain the lifecycle-callback and resource-cleanup differences between singleton and prototype beans, and how you'd reliably clean up a prototype that holds a resource.

level: seniorimportance: should knowfreq 55%

answer

  1. singleton: init + destroy both run
  2. prototype: init runs, destroy NEVER
  3. container keeps no reference to prototype
  4. AutoCloseable + try-with-resources for cleanup
  5. destroyMethod recorded but not auto-called on prototype

basics

~20 s

For singletons Spring runs both init (@PostConstruct) and destroy (@PreDestroy) callbacks. For prototypes it runs init but never destroy — the container stops tracking them. Clean up prototypes yourself, or via a custom destroy hook you invoke.

solid answer

~40 s

Spring fully manages a singleton's lifecycle: it calls initialization callbacks (@PostConstruct, InitializingBean.afterPropertiesSet, @Bean initMethod) at creation and destruction callbacks (@PreDestroy, DisposableBean.destroy, @Bean destroyMethod) at context shutdown. For prototypes, Spring runs the same initialization callbacks but does NOT call destruction callbacks and does not retain a reference — the caller owns the instance thereafter. So a prototype holding a file handle, socket, or connection will leak if you rely on @PreDestroy. Reliable cleanup options: (1) make the prototype implement Closeable/AutoCloseable and use it in try-with-resources; (2) obtain instances via ObjectProvider and explicitly close them; (3) register a custom DestructionAwareBeanPostProcessor or call a configured destroy method manually; (4) let the requesting singleton drive teardown. Never assume container shutdown cleans prototypes.

code

java · 18 lines
java
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
class DbCursor implements AutoCloseable {
    @PostConstruct void open()  { /* acquire */ }   // runs on creation
    @PreDestroy   void never() { /* NOT called by Spring for prototypes */ }
    public void close()         { /* release */ }    // you must call this
}

@Service
class Reader {
    private final ObjectProvider<DbCursor> cursors;
    Reader(ObjectProvider<DbCursor> cursors) { this.cursors = cursors; }
    void read() {
        try (DbCursor c = cursors.getObject()) {   // deterministic cleanup
            c.toString();
        }
    }
}

go deeper

for a junior

Know that singletons get both callbacks and prototypes don't get the destroy callback.

for a middle

Explain that init still runs on prototypes and that cleanup is the caller's responsibility.

for a senior

Give concrete, reliable cleanup patterns (AutoCloseable/try-with-resources, provider + finally) and explain why the container can't track prototypes.

for a principal

Weigh strategies (owning-bean teardown, DestructionAwareBeanPostProcessor) against leak/testability risk and design APIs so prototype ownership is explicit.

## Lifecycle callbacks recap Spring supports three equivalent callback styles: - **Initialization**: `@PostConstruct`, `InitializingBean.afterPropertiesSet()`, or `@Bean(initMethod = "...")`. - **Destruction**: `@PreDestroy`, `DisposableBean.destroy()`, or `@Bean(destroyMethod = "...")`. ## Singleton: fully managed both ends Because the container caches and owns the single instance for the whole context lifetime, it invokes **init callbacks at creation** and **destroy callbacks when the `ApplicationContext` closes** (`context.close()`, JVM shutdown hook, or web app undeploy). This makes singletons ideal for holding long-lived resources (connection pools, caches) that must be released at shutdown. ## Prototype: init yes, destroy NO For prototypes Spring: 1. instantiates, 2. populates dependencies, 3. runs `BeanPostProcessor`s and **initialization** callbacks, 4. **hands the fully-initialized object to the caller and forgets it.** The container keeps **no reference**, so it *cannot* and *does not* call `@PreDestroy` / `destroy()` — not even at context shutdown. From the reference docs: *"the container instantiates, configures, and otherwise assembles a prototype object and hands it to the client, with no further record of that prototype instance."* Consequently **destruction lifecycle is the client's responsibility**. > This is often the *point* of the question: candidates who think closing the context cleans up prototypes will leak resources. ## Why the asymmetry exists The container has no way to know when a caller is *finished* with a prototype — it could be held anywhere, for any duration, garbage-collected whenever. Tracking every prototype to fire destroy callbacks would require the container to retain references, which would (a) prevent GC (memory leak) and (b) contradict the 'create and forget' semantics. ## Reliable cleanup strategies **1. AutoCloseable + try-with-resources (simplest):** ```java @Component @Scope("prototype") class Session implements AutoCloseable { public void close() { /* release */ } } try (Session s = provider.getObject()) { s.doWork(); } // deterministically closed ``` **2. Explicit provider + manual teardown:** get via `ObjectProvider<Session>`, then call your cleanup in a `finally`. **3. Custom `DestructionAwareBeanPostProcessor`:** you can register one and manually invoke its `postProcessBeforeDestruction` — but Spring won't call it automatically for prototypes; you'd still trigger cleanup yourself. This is rarely worth it versus AutoCloseable. **4. `@Bean(destroyMethod=...)` on a prototype:** the destroyMethod is recorded but **still not auto-invoked** for prototypes; you must call it. Spring even warns/skips inferred `close`/`shutdown` destroy methods for prototype-scoped beans. **5. Let the owning singleton manage it:** the singleton that requests prototypes tracks and closes them as part of its own `@PreDestroy`. ## Related gotchas - **Prototypes are lazy**: never created at startup, only on request — so init callbacks fire at *first request*, not at context refresh. - **A prototype depended-on by a singleton** is created once (during the singleton's creation) and shares the singleton's lifetime — another reason its destroy semantics are murky. - **Thread-safety** of singletons: init/destroy run once, but business methods run concurrently; keep singleton state immutable or synchronized.

  • If you put @PreDestroy on a prototype, when does it run?
    Never automatically. Spring stops tracking prototypes after handing them off, so it doesn't invoke @PreDestroy or DisposableBean.destroy on them — even at context shutdown. You must trigger cleanup yourself.
  • Does @Bean(destroyMethod="close") make Spring close a prototype at shutdown?
    No. The destroy method is registered but not auto-invoked for prototype-scoped beans; Spring even skips inferred close/shutdown methods for prototypes. You call it manually or use AutoCloseable/try-with-resources.

saying these in an interview costs you the question

  • Assuming context.close() runs @PreDestroy on prototypes.
  • Relying on GC/finalizers to release prototype resources.
  • Not realizing init callbacks DO run on prototypes (only destroy is skipped).
  • Thinking a destroyMethod is enough for prototype cleanup.

context