Spring BootSpring Corebeginner
Updated:

Spring Bean Lifecycle: From Instantiation to Destruction

6 min read

The exact order Spring follows to create, wire, initialise and destroy a bean, plus the callbacks you can hook into at each phase.

TL;DR – Quick Answer

The Spring bean lifecycle is the sequence the container follows for every managed object: it instantiates the bean, injects its dependencies, runs initialisation callbacks like @PostConstruct, keeps the bean ready for use, and finally runs destruction callbacks like @PreDestroy when the context closes. You hook into these phases with annotations or lifecycle interfaces to run setup and cleanup code at the right moment.

On This Page

Every object Spring manages passes through the same predictable journey: it is born, wired to its collaborators, prepared for work, kept alive while the application runs, and finally shut down. Understanding that journey is the difference between guessing why an autowired field is null in a constructor and knowing exactly which phase you are in. This tutorial walks the whole path with runnable code and the log output to prove the order.

Why the lifecycle exists at all

A Spring bean is just a normal Java object whose creation Spring takes over. Because Spring controls creation, it also controls timing — when your fields get populated, when it is safe to open a database pool, and when to release that pool. Instead of scattering new and cleanup calls across your code, you declare hooks and Spring calls them at the guaranteed moment.

This matters most for resources that are expensive or external: connection pools, caches, scheduled tasks, message listeners. You want them started only after all dependencies are in place, and stopped cleanly before the application exits. The lifecycle callbacks give you exactly those two anchor points.

The full sequence, phase by phase

For a singleton bean the container follows this order:

  1. Instantiation — Spring calls the constructor.
  2. Dependency injection — fields and setters are populated (this is where @Autowired does its work).
  3. Aware callbacks — if the bean implements BeanNameAware, ApplicationContextAware, etc.
  4. BeanPostProcessor before-initpostProcessBeforeInitialization runs.
  5. @PostConstruct — your annotated init method runs.
  6. InitializingBeanafterPropertiesSet() runs if implemented.
  7. Custom init method — the method named in @Bean(initMethod = "...").
  8. BeanPostProcessor after-initpostProcessAfterInitialization runs; proxies are often created here.
  9. Bean is ready — it lives in the container and serves requests.
  10. @PreDestroy — runs when the context is closing.
  11. DisposableBeandestroy() runs if implemented.
  12. Custom destroy method — the method named in @Bean(destroyMethod = "...").

Pro tip: Steps 5–7 all do "initialisation", but they run in that fixed order. If you implement more than one, remember @PostConstruct always wins the race. Interviewers love to ask which runs first when a class has both @PostConstruct and afterPropertiesSet().

Seeing the order in real code

The cleanest way to internalise the sequence is to print a line from each phase and read the log. Here is a bean that touches every callback:

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
public class PaymentGateway implements InitializingBean, DisposableBean {

    public PaymentGateway() {
        System.out.println("1. Constructor: object created");
    }

    @PostConstruct
    public void warmUp() {
        System.out.println("2. @PostConstruct: dependencies ready, opening pool");
    }

    @Override
    public void afterPropertiesSet() {
        System.out.println("3. afterPropertiesSet: InitializingBean callback");
    }

    public void charge() {
        System.out.println("-> charge() called while bean is in use");
    }

    @PreDestroy
    public void shutDown() {
        System.out.println("4. @PreDestroy: releasing pool");
    }

    @Override
    public void destroy() {
        System.out.println("5. destroy: DisposableBean callback");
    }
}

Now drive it from a small runner that opens and then deliberately closes the context:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class LifecycleDemo {
    public static void main(String[] args) {
        var context = new AnnotationConfigApplicationContext(LifecycleDemo.class);
        PaymentGateway gateway = context.getBean(PaymentGateway.class);
        gateway.charge();
        context.close(); // triggers destruction callbacks
    }
}

Run it and the console prints the numbers in order: constructor, @PostConstruct, afterPropertiesSet, then charge(), and on close() the @PreDestroy and destroy lines. That printed order is the single most useful thing to memorise about this topic.

Why the constructor cannot see injected fields

A question that trips up almost every beginner: why is an @Autowired field null inside the constructor? Because injection happens in phase 2, after the constructor of phase 1 has already finished. The object must exist before Spring can push dependencies into it.

@Component
public class ReportService {
    @Autowired
    private PaymentGateway gateway; // still null in the constructor

    public ReportService() {
        // gateway is null here — do NOT call gateway.charge()
    }

    @PostConstruct
    public void init() {
        gateway.charge(); // safe: injection is complete
    }
}

Common mistake: Calling an injected dependency from the constructor throws a NullPointerException at startup. Move that logic into a @PostConstruct method, or better, switch to constructor injection so the dependency arrives as a parameter and can never be null. Constructor injection is the pattern we teach first in the Java Full Stack program.

Custom init and destroy without annotations

When you define beans in a @Configuration class rather than by component scanning, you can name init and destroy methods directly on the @Bean declaration. This is common for third-party classes you cannot annotate:

@Configuration
public class CacheConfig {
    @Bean(initMethod = "start", destroyMethod = "stop")
    public MetricsCache metricsCache() {
        return new MetricsCache();
    }
}

Spring calls start() during initialisation and stop() on shutdown. For many well-known types, Spring even infers destroyMethod automatically if the class exposes a public close() or shutdown() method, so you often do not need to name it.

How scope changes the picture

The lifecycle above describes a singleton — the default. If you change the bean scope to prototype, Spring creates a fresh instance on every request and runs the initialisation callbacks, but it then forgets the bean. Destruction callbacks never fire for prototypes, so a @PreDestroy on a prototype bean is silently useless. This asymmetry is a favourite interview trap.

Singletons, by contrast, are created eagerly at startup and destroyed exactly once when the context closes. That predictability is why nearly every service, repository and controller in a typical Spring Boot application is a singleton.

BeanPostProcessor: where the framework itself hooks in

The lifecycle is not just for your code — Spring uses the same hooks internally. A BeanPostProcessor is a special bean that Spring calls before and after every other bean's initialisation. This is the extension point through which features like AOP proxies, @Async wrappers and property validation are inserted.

import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class TimingPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String name) {
        System.out.println("Bean ready: " + name);
        return bean; // could return a proxy that wraps the original
    }
}

The critical detail is that postProcessAfterInitialization may return a different object — a proxy that wraps the original bean. That is exactly how Spring adds transactional behaviour behind @Transactional: the bean you receive from the container is not your class but a proxy around it. Knowing this explains a puzzling symptom beginners hit — a @Transactional method calling another method in the same class does not start a new transaction, because the internal call bypasses the proxy.

Eager vs lazy initialisation

By default, singleton beans are created eagerly when the context starts. This is deliberate: you want configuration mistakes to fail fast at startup rather than on the first request in production. If a bean cannot be built, the application refuses to start and tells you why.

Sometimes you want to defer creation — for an expensive bean that is rarely used. Mark it @Lazy and Spring delays its instantiation and initialisation until something first asks for it:

@Component
@Lazy
public class ReportGenerator {
    public ReportGenerator() {
        System.out.println("Built only on first use");
    }
}

The trade-off is real. Eager creation surfaces problems immediately; lazy creation saves startup time and memory but pushes any failure to the moment of first use. For most application beans, prefer eager. Reserve @Lazy for genuinely heavy, seldom-touched components. Either way, the same lifecycle callbacks run — @Lazy only changes when they run, not the order.

Interview relevance

Bean lifecycle questions separate candidates who have read a tutorial from those who have watched their own logs. Be ready to state the callback order from memory, explain why constructor injection avoids the null-field problem, and describe when @PreDestroy does not run. If you can also mention that BeanPostProcessor is how Spring inserts AOP proxies between init phases, you signal genuine depth. Pair this topic with dependency injection and the annotation questions in the Spring Boot annotations interview set, because interviewers almost always ask them together.

Frequently Asked Questions

What is the correct order of Spring bean lifecycle callbacks?
First the constructor runs, then dependencies are injected, then any BeanPostProcessor before-init methods run, then @PostConstruct, then afterPropertiesSet from InitializingBean, then any custom init method. On shutdown the order is @PreDestroy, then destroy from DisposableBean, then the custom destroy method.
What is the difference between @PostConstruct and a constructor?
The constructor runs before Spring has injected any dependencies, so autowired fields are still null there. @PostConstruct runs after injection is complete, which makes it the correct place for setup logic that needs those dependencies. Use the constructor only for assigning fields, not for initialisation work.
Are @PreDestroy callbacks guaranteed to run?
They run for singleton beans when the application context is closed gracefully, for example on a normal shutdown or context.close(). They do not run for prototype beans, because Spring stops managing a prototype after creation, and they may be skipped if the JVM is killed abruptly.
Should I use @PostConstruct or InitializingBean?
Prefer @PostConstruct because it keeps your class free of Spring interfaces and stays portable. InitializingBean and DisposableBean couple your code to the framework, which is why most teams reserve them for infrastructure code. Both achieve the same result for ordinary initialisation.
Do prototype beans have a full lifecycle in Spring?
Only partly. Spring instantiates, injects and runs the initialisation callbacks for a prototype bean, but it does not track it afterwards. That means destruction callbacks like @PreDestroy never fire for prototypes, so you are responsible for any cleanup yourself.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Explore the Program

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 15 July 2026 LinkedIn
Chat with us