The Boot Kernel: All Twelve Steps of refresh()
A beginner's first reaction to twelve method names is "I cannot memorize this". This article is really about one thing: the single line new AnnotationConfigApplicationContext(...) kicks off a pipeline with a strict order — it first settles which objects will exist, then mounts the hooks that decide how objects get processed, and only at the very end does it start building objects. Get that order wrong and you get the hardest class of bugs: "my AOP quietly stopped working", "startup blew up halfway". So do not memorize twelve names; memorize three time windows.
Terms that recur below, one line each:
- Container: the "housekeeper" object (
ApplicationContext) that creates and manages your objects; you ask it for things withgetBean - Bean: a plain Java object handed to the container to create and manage
- BeanDefinition: data describing how to build one bean (class name, singleton or prototype, lazy or not) while the object still does not exist
refresh(): the twelve-step routine by which the container turns itself from an empty shell into something ready to serve; defined onAbstractApplicationContext- BeanFactoryPostProcessor (BFPP): a processor that rewrites which beans exist — before any object is built. The person who edits the blueprints
- BeanPostProcessor (BPP): a hook fired once right before and once right after each individual object is born. The person who reworks the finished dish
@PostConstruct: a method annotation invoked automatically once the object itself is constructed and its fields filled — the factory self-check- Runner (
ApplicationRunner/CommandLineRunner): startup tasks invoked only after the container is fully ready - Reflection: calling a constructor by name at runtime. The container never binds your class at compile time; it builds objects reflectively from the class name stored in the definition
refresh() is the whole process of opening a shop. Picking the site = preparing the Environment (step 1: check water, power and rent — i.e. config and placeholders); drawing the blueprints = producing BeanDefinitions (step 2: plans only, no building yet); running utilities and buying kitchen gear = installing the container's built-ins (steps 3–4); redrawing the plans during renovation = BeanFactoryPostProcessor (step 5: walls can still move, rooms can still be added); etiquette training for staff = BeanPostProcessor registration (step 6: rules must be taught before anyone starts work); installing the till and the PA system = MessageSource and the event machinery (steps 7–10); cooking everything in advance for the soft opening = pre-instantiating all non-lazy singletons (step 11, the slowest); cutting the ribbon = publishing ContextRefreshedEvent (step 12, officially open); opening the doors to customers = running Runners (already outside refresh() — only now can guests walk in).

two people on this site are the easiest to confuse — the one with the red pen rewriting the blueprints (BeanFactoryPostProcessor, step 5) and the one who reworks a dish already served at the table (BeanPostProcessor, on duty after step 6). The first decides whether a dish is on the menu at all; the second can only decide how it is plated. Section 2 pins that boundary down with a comparison chart.
After this article you should be able to answer three questions:
- At which step does the
@AutowiredI wrote actually start to work — and why does it not exist at all during step 5? - Which step
news a non-lazy singleton, and what happens to everything already built if that step fails? - Should my custom logic hang on a BFPP, a BPP,
@PostConstructor a Runner? What can each of them see, and what can they not?
Almost every Spring tutorial begins with this one line, yet few stop to ask: how much does it actually trigger?
package com.example;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class BootDemo { public static void main(String[] args) { // This plain-looking line hides the twelve steps of refresh() AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); HelloService hello = ctx.getBean(HelloService.class); System.out.println(hello.sayHello("Spring")); ctx.close(); }}new AnnotationConfigApplicationContext(AppConfig.class)does just two things in its constructor: registerAppConfigas a configuration class, then callrefresh()on the last line- All the real work lives inside
refresh()— the constructor parses no annotations and creates noBean getBean(...)returns instantly only becauserefresh()has already pre-instantiated every singleton
The equivalent form is three merged statements: new AnnotationConfigApplicationContext() → register(AppConfig.class) → refresh(). Remember this equality and everything later becomes clearer.

refresh() belongs to AbstractApplicationContext; stripped of exception handling, its skeleton is:
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { prepareRefresh(); // step 1 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 2 prepareBeanFactory(beanFactory); // step 3 try { postProcessBeanFactory(beanFactory); // step 4 invokeBeanFactoryPostProcessors(beanFactory); // step 5 registerBeanPostProcessors(beanFactory); // step 6 initMessageSource(); // step 7 initApplicationEventMulticaster(); // step 8 onRefresh(); // step 9 registerListeners(); // step 10 finishBeanFactoryInitialization(beanFactory); // step 11: the main act finishRefresh(); // step 12 } catch (BeansException ex) { destroyBeans(); cancelRefresh(ex); throw ex; } }}- The whole method is wrapped in
synchronized, so the same context cannot refresh concurrently - No try block covers steps 1–3, so a failure there skips destruction; a failure after step 4 triggers
destroyBeans() - When reading framework source, skim this skeleton first and then drill down — far more efficient than jumping around blindly

The table below is the heart of this article — worth bookmarking. Each step lists what it does, which method runs it, and where you can hook in:
| Step | Key method | Job of this step | Your insertion point |
|---|---|---|---|
| 1 | prepareRefresh | Stamp the start time, mark active, validate required placeholders | ApplicationContextInitializer, override initPropertySources |
| 2 | obtainFreshBeanFactory | Create a DefaultListableBeanFactory and parse config/XML into BeanDefinitions | BeanDefinitionRegistryPostProcessor can add more definitions |
| 3 | prepareBeanFactory | Install built-ins: ClassLoader, ApplicationContextAwareProcessor, ignored dependency interfaces | Override prepareBeanFactory to append ignores |
| 4 | postProcessBeanFactory | A hook for subclasses; definitions are frozen, no instance exists yet | Override in a subclass (e.g. WebApplicationContext) |
| 5 | invokeBeanFactoryPostProcessors | Run all BFPPs, parse @Configuration / @Bean / @Import, finalize definitions | BeanFactoryPostProcessor / BeanDefinitionRegistryPostProcessor |
| 6 | registerBeanPostProcessors | Instantiate and sort all BeanPostProcessors | Implement BeanPostProcessor and make it Ordered |
| 7 | initMessageSource | Register the internationalization MessageSource singleton | Register a bean named messageSource |
| 8 | initApplicationEventMulticaster | Register the event multicaster (a simple synchronous one by default) | Register a bean named applicationEventMulticaster |
| 9 | onRefresh | A hook for subclasses; Spring Boot starts the web server here | Override in a subclass (e.g. ServletWebServerApplicationContext) |
| 10 | registerListeners | Register ApplicationListeners and dispatch early events | ApplicationListener |
| 11 | finishBeanFactoryInitialization | Pre-instantiate all non-lazy singletons — the most expensive step | BeanPostProcessor, SmartInitializingSingleton |
| 12 | finishRefresh | Initialize the lifecycle processor and publish ContextRefreshedEvent | ApplicationListener<ContextRefreshedEvent> |
steps 1–4 "clean the house" (prepare the environment, build the factory, install built-ins); steps 5–6 "set the rules" (definitions and post-processors); steps 7–10 "ready the infrastructure" (events and listeners); and only steps 11–12 actually "produce objects and declare the shop open". Read the twelve steps with this rhythm and they stop being a list of names to memorize.
Twelve names are table-memorization, but order has to be clicked by hand. The debugger below shows the skeleton of refresh() on the left while the right panel refreshes "how many definitions and how many objects exist right now". Press next through all nine cells and watch two numbers in particular: after cell ⑥ singletonObjects is still 0, after cell ⑦ it is still 0 — only cell ⑨ makes it jump.
public void refresh() { // AbstractApplicationContext synchronized (startupShutdownMonitor) { // 1. one lock covers both startup and shutdown prepareRefresh(); // 2. step 1: stamp the time, validate placeholders bf = obtainFreshBeanFactory(); // 3. step 2: factory born, definitions enter the table prepareBeanFactory(bf); // 4. step 3: install the built-in kit postProcessBeanFactory(bf); // 5. step 4: the subclass fallback hook invokeBeanFactoryPostProcessors(bf); // 6. step 5: last window to edit definitions registerBeanPostProcessors(bf); // 7. step 6: hooks take their posts ... // 8. steps 7-10: i18n, events, listeners finishBeanFactoryInitialization(bf); // 9. step 11: pre-instantiate every singleton finishRefresh(); // 10. step 12: publish ContextRefreshedEvent }}| container state | active = true |
| beanDefinitionCount | 0 |
| singletonObjects | 0 |
refreshprepareRefreshThe hardest decision for a beginner is genuinely where my own logic should hang. The sandbox in Section 10 turns the four candidate moments into a single switch — flip one position and it immediately tells you what that step can see and cannot see. Work through it after reading Sections 3 and 4.

The two rows above most often confused are step 5 and step 6, because their English names differ by a single word (both say "post-processor") while they do opposite jobs. Keep them firmly apart:
the BFPP at step 5 redraws the blueprints — nothing is built yet, so it can decide how many rooms exist and which one becomes the storeroom. The BPP that takes duty after step 6 reworks an already served dish — it can sprinkle pepper or change the plate (wrap an AOP proxy), but the menu was fixed long ago. To change whether a dish exists you must act during the blueprint phase; to change what a plate looks like you can only act after cooking. That single sentence is the point of the whole article.

Now zoom in on one class you wrote: turning from a sheet of paper into the dish on the table crosses four of the twelve steps — 2, 5, 6 and 11. This animation walks that exact route, showing what each step touches: a definition or an object.

This step is the most underrated. It does far more than "run a few post-processors" — it decides which beans exist at all:
// Key logic inside AbstractApplicationContext#invokeBeanFactoryPostProcessors// 1. First run BeanDefinitionRegistryPostProcessors (they may add definitions)// The heavyweight among them is ConfigurationClassPostProcessor// 2. Then run ordinary BeanFactoryPostProcessors (they only edit definitions)PostProcessorRegistrationDelegate .invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());ConfigurationClassPostProcessor scans packages named by @ComponentScan → parses @Configuration classes → handles @Bean methods, @Import, @PropertySource → and translates all of it into BeanDefinitions. Whether your annotations take effect is decided here, not in the constructor.
Attention: by the end of this step, the definitions in `beanDefinitionMap` are **essentially final**, and **not a single singleton has been created**. This is the last window to change definitions — and the foothold of auto-configuration.
BeanPostProcessors (BPPs) wrap every bean's initialization, and their order decides the sequencing of AOP proxies, @Autowired resolution and @PostConstruct. Spring therefore grades them carefully:
// Precedence: PriorityOrdered > Ordered > unordered (in registration order)// The result is split into two groups: ordinary BPPs and MergedBeanDefinitionPostProcessors// The former register first; the latter are called back to rewrite merged definitions at pre-instantiation- BPPs implementing
PriorityOrderedregister first - Those implementing
Orderedcome next - The rest come last, in indeterminate order — so a custom BPP must implement
Orderedif its order matters
Trap: a custom BeanPostProcessor that does not implement Ordered may interact with other BPPs in an order opposite to what you expect (for example, fetching an already-proxied bean inside your BPP), producing a "sometimes right, sometimes wrong" bug.
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { // e.g. prepare a ConversionService early for @Autowired if (beanFactory.containsBeanDefinition(CONVERSION_SERVICE_BEAN_NAME)) { /* ... */ } // Freeze all definitions; no further changes allowed beanFactory.freezeConfiguration(); // The core: pre-instantiate every non-lazy singleton beanFactory.preInstantiateSingletons();}preInstantiateSingletons() iterates over every beanDefinitionNames, skips abstract, non-singleton and lazy-init=true definitions, and calls getBean(name) for the rest. Every "constructor invoked, @PostConstruct executed, AOP proxy created" you ever see in business code happens here. After the loop, it also calls back every bean implementing SmartInitializingSingleton.
protected void finishRefresh() { clearResourceCaches(); // clear caches initLifecycleProcessor(); // lifecycle processor ready getLifecycleProcessor().onRefresh(); // start components implementing Lifecycle publishEvent(new ContextRefreshedEvent(this)); // the key: publish the refresh event}ContextRefreshedEventis the most widely used startup hook: cache warm-up, connection-pool init and one-time data loading can all listen to it- Be careful though: this event is published multiple times (child contexts publish it too), so always check whether
event.getApplicationContext()is the one you care about - The
start()ofLifecyclecomponents also fires here — inSpring Bootthe embedded Tomcat startup order is related to this
Set logging.level.org.springframework=DEBUG and you will see the log below. I have tagged each line with its step:
# Step 1 · prepareRefresh2024-06-01 17:02:11.301 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@1a2b3c4d# Step 5 · invokeBeanFactoryPostProcessors (ConfigurationClassPostProcessor at work)2024-06-01 17:02:11.412 [main] INFO o.s.c.a.ConfigurationClassPostProcessor - Cannot enhance @Configuration bean definition 'AppConfig' since its singleton instance has been created too early# Step 6 · registerBeanPostProcessors2024-06-01 17:02:11.470 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'# Step 11 · finishBeanFactoryInitialization (many singletons are new-ed here)2024-06-01 17:02:11.520 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'helloService'2024-06-01 17:02:11.523 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'userService'# Step 12 · finishRefresh2024-06-01 17:02:11.589 [main] DEBUG o.s.c.e.EventPublicationInterceptor - ...2024-06-01 17:02:11.610 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Started BootDemo in 0.312 secondsRefreshing ...is the opener printed at step 1;Started ... in x secondsis the closer at step 12 — that short span is the whole startup- The flood of
Creating shared instance of singleton bean 'xxx'clusters in step 11, and this segment usually accounts for 80%+ of startup time - A "created too early" warning almost always means you fell into the trap in Section 7
Key point: to tune startup speed, sort the step-11 Creating shared instance lines by duration — that is the most direct optimization checklist. Whatever should not be created at startup can be deferred with @Lazy.
The catch block of refresh() is short but heavy with meaning:
catch (BeansException ex) { destroyBeans(); // destroy every singleton created so far — no half-baked leaks cancelRefresh(ex); // clear the active flag and reset the context state throw ex; // rethrow so the caller knows startup failed}destroyBeans()walks the registered singletons and invokes their destroy callbacks — a graceful wrap-up even on failurecancelRefresh()resetsactiveto false so the context may be refreshed again- Note: the thrown exception is usually a
BeanCreationException, with the real cause buried ingetMostSpecificCause()
Spring Boot layers a FailureAnalyzer on top, turning ugly stack traces into plain language:
public class MyPortFailureAnalyzer extends AbstractFailureAnalyzer<BindException> { @Override protected FailureAnalysis analyze(Throwable rootFailure, BindException cause) { return new FailureAnalysis( "Port already in use, cannot start the server: " + cause.getMessage(), "Change server.port or kill the process holding the port.", cause); }}- Register it via
META-INF/spring.factories(Boot 3 usesMETA-INF/spring/org.springframework.boot.diagnostics.FailureAnalyzer.imports) - Boot ships over a dozen analyzers (port conflict, missing DataSource, absent config...), which turn "startup failure" from torture into a hint
Those cleanup steps run in a fixed order. The animation plays in a few seconds, but it decides whether a production incident reads as "failed cleanly" or "left a half-built container behind":

The lifecycle processor installed at step twelve also runs the shutdown in reverse: when the context closes, each phase (SmartLifecycle groups by role) has to stop inside its time budget. Drag that number once and its relationship with the orchestrator becomes concrete:
- Boot defaults to 30s and Kubernetes terminationGracePeriodSeconds also defaults to 30s
- Do not enable server.shutdown=graceful and then add 25s of business cleanup — the two budgets squeeze each other
- Past the deadline you get one log line, not an exception
never try { refresh(); } catch (Exception e) { / ignore / }. A failed startup leaves the container half-initialized; using it further will plant hard-to-diagnose null pointers.
Q1: Why is refresh() guarded by synchronized?
// AbstractApplicationContextprivate final Object startupShutdownMonitor = new Object();public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { /* ... */ }}refresh()andclose()share the same lock, preventing a race between "starting up" and "shutting down"- It also guarantees the same context is never refreshed twice concurrently, which would duplicate singletons and events
- Note the lock is method-level serialization, unrelated to business threads; once startup finishes it no longer applies
Q2: How do SpringApplication.run() and refresh() relate?
run()is theSpring Bootfacade: prepare theEnvironment→ print the Banner → create theApplicationContext→ callrefreshContext()(which isrefresh()) → runApplicationRunner/CommandLineRunner- In other words,
refresh()is just one key sub-step ofrun();run()also prepares the environment, wires listeners, prints the Banner and runs runner callbacks - One sentence to remember:
refresh()governs "inside the container";run()governs "assembling the whole application"
| Dimension | refresh() | SpringApplication.run() |
|---|---|---|
| Owning class | AbstractApplicationContext | SpringApplication |
| Main job | Refresh the container: definitions, post-processors, pre-instantiation | Assemble the app: environment, Banner, listeners, runners |
| Containment | Called by refreshContext() | Calls refresh() internally |
| Exception semantics | Throws BeansException (e.g. BeanCreationException) | May aggregate into IllegalStateException or rethrow |
calling getBean() inside a BeanFactoryPostProcessor triggers early instantiation. Many people want to "check whether a bean can be created" inside a BFPP and write beanFactory.getBean("userService") — this instantly instantiates userService while no BeanPostProcessor is registered yet, so it gets no AOP proxy and @Autowired is never processed, yielding a crippled bean. The correct approach is to operate on BeanDefinitions, not objects.
timing of manual setXxx before refresh(). Calls like ctx.setParent(...), ctx.register(...), registerBeanDefinition(...) must happen before refresh(), otherwise they throw IllegalStateException (the container is active). After step 11 completes, singletons are pooled and definitions frozen — too late to change anything.
The demo below visualizes "how each definition changes state in the Beans list". Match it against steps 5–11 to see which beans each step pushes to "created":
Memorizing method names does not work; making them move does. The four labs below are ordered "main line → hooks → the definition table → outside the container", and each needs only the parameter switch in the corner.
The first lab is the main line. Pick "All twelve" and watch them light up one by one, noticing which ones are marked as critical; then switch to "The four that matter" and you will find that invokeBeanFactoryPostProcessors, registerBeanPostProcessors, finishBeanFactoryInitialization and finishRefresh decide 90% of the behaviour, while the other eight merely glue them together:
The second lab targets the place beginners most often go wrong — the timing of the hooks. Pick "Post-processors" and watch steps 3 and 4: once a bean is born early because a BPP depended on it, it escapes processing for life, which is exactly where the log line is not eligible for getting processed by all BeanPostProcessors comes from:
The third lab answers "where does the blueprint actually live?". Pick "The registry" and see that beanDefinitionMap holds only descriptions while singletonObjects is still empty — the root of the trap in Section 7, where calling getBean() at the definition stage is queue-jumping:
The fourth lab zooms out: refresh() is only step ④ of SpringApplication.run(). Pick "The eight steps" against the opening-day timeline, then switch to "Runners" to confirm your own startup logic runs only after the ribbon — which directly determines which row of the sandbox in Section 11 you should pick:
Inside the batch of hooks registered at step six sits the one that decides whether a bean deserves a proxy — the actual gate for AOP. Switch to "Interception in BPP" and watch it question every candidate in turn; that is also why the Section 7 line not eligible for getting processed by all BeanPostProcessors really means "this object escaped proxying":
And when step eleven actually births a bean, it follows a fixed eight-station pipeline. This lab expands the part after preInstantiateSingletons — run the singleton route first, then "Watch destroy" to see exactly whom destroyBeans() from Section 5 calls:
Enough pressing buttons — type the commands yourself. This console talks to the real container executing in your browser, and every response from beans, conditions and query is computed by the kernel. Run them in this order and you are stepping through refresh() itself:
type beans immediately after boot. The list contains your business beans and a crowd of names starting with org.springframework. — those are the standard fittings installed at step three ("running the utilities"). When you cannot tell yours from the framework's, the prefix is the fastest filter.
The beginner's pain here is very concrete: for one requirement, the number of objects visible differs wildly across four different hook points. At step 5 there is not a single instance; at Runner time it is arguably too late (the embedded container is already taking traffic). The sandbox below turns the four candidates into one switch — flip one position and you immediately see that step's field of view and its danger:
Position: refresh() step 5, invokeBeanFactoryPostProcessorsSees: every definition in beanDefinitionMap (about 40), Environment, ${} placeholdersCannot see: any business object — getBean("userService") forces it into existenceDangerous move: fetching a bean here makes it escape all later BPP processingGood for: bulk edits to scope / lazy, injecting extra definitions, swapping implementations
work through all four positions in sequence and the only dividing line you will find is "has the object been born yet". Step 5 has blueprints, step 6 has hooks, step 11 has finished objects, and the Runner has the whole shop. Selection mnemonics: change whether it exists → BFPP; change what it looks like → BPP; only mind yourself → @PostConstruct; need everything ready → Runner.
A warm-up question, whose answer sits in the refresh lab of Section 10:
The heavy one, matching Section 3.2 and the second row of the Section 11 sandbox:
Every "error text" row below can be copied verbatim into a search engine. They share one trait: the exception tells you which step failed, while the disease is written in the last line of the caused by chain.
| Error text (fragment) | Real cause | 30-second fix | Deep dive | |
|---|---|---|---|---|
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService' defined in class path resource [com/example/AppConfig.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is ... NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.OrderRepository' available | During step 11 pre-instantiation, a singleton's dependency is absent from the registry. Usually that class lacks @Service / @Component, or its package is outside the @ComponentScan range | There is exactly one way to read it: start from the last Caused by and work outwards — the root cause is always innermost. The outer chain of Error creating bean with name is only collateral damage. Search Error creating bean with name in your IDE and the first hit is the real culprit | Section 5 · Article 7 BeanDefinition | |
Web server failed to start. Port 8080 was already in use. (the underlying exception is org.springframework.boot.web.server.PortInUseException) | The embedded Tomcat started during step 12 finishRefresh and found the port taken. Thousands of beans were already built before the failure, which makes it look half-successful when in fact everything is discarded | Boot already translates it into APPLICATION FAILED TO START plus an ACTION paragraph. Find the holder: `netstat -ano \ | findstr :8080 on Windows, lsof -i :8080 on macOS/Linux; use server.port=0` for a random port as a temporary dodge | Section 4 log mapping · Article 5 your first project |
org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'helloService' defined in URL [file [./beans.xml]]: There is already [Generic bean: class [com.example.HelloServiceImpl]] bound. | The same beanName was registered twice with incompatible definitions. Since Boot 2.1 spring.main.allow-bean-definition-overriding defaults to false, so step 5 rejects it outright | Nine times out of ten it is XML mixed with annotations, or two overlapping @ComponentScan ranges scanning the same family twice. Read only two parts of the error: the conflicting name and the two sources after defined in. If you truly need the override, open the flag explicitly (not recommended long-term) | The trap in Section 7 · Article 7 BeanDefinition | |
java.lang.IllegalStateException: org.springframework.context.annotation.AnnotationConfigApplicationContext@... has been closed already (on a second refresh() of the same context you first see Exception encountered during context initialization - cancelling refresh attempt: ... in the log, and fetching a bean reports ... has not been refreshed yet) | You operated on the same context outside the legal window: calling getBean after close(), or calling register(...) + refresh() twice while hand-rolling a container. The startupShutdownMonitor lock prevents concurrency, not repetition | Keep the one-shot flow in your head: new → register → refresh() → use → close(), each exactly once. For a clean state, build a new context; in tests let @SpringBootTest own the lifecycle instead of calling refresh() yourself | Q1 in Section 6 | |
No qualifying bean of type 'com.example.Notifier' available: expected single matching bean but found 2: smsNotifier,mailNotifier (what is actually thrown is NoUniqueBeanDefinitionException, a subclass of NoSuchBeanDefinitionException) | The definition stage is fine; ambiguity appears at type-based resolution: two candidates of the same type and no disambiguation hint at the injection point | Three fixes in priority order: add @Qualifier("smsNotifier") at the injection point; mark the preferred one @Primary; if you really want both, declare List<Notifier> and let the container inject both | The bd lab in Section 10 · Article 6 IoC and DI | |
The one with no exception at all: the startup log shows Bean 'dataSource' of type [...] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying), and @Transactional / your custom aspect on that bean then quietly stops working | Your custom BeanPostProcessor depends on an ordinary bean (or injects a DataSource directly). To satisfy that BPP, the bean was forced into existence before step 6, so it skipped postProcessAfterInitialization and was never wrapped in a proxy | Pick any of three fixes: inject ObjectProvider<T> or a @Lazy proxy into the BPP; implement BeanFactoryAware and fetch it later; hand the proxying job back to the AOP side. Always implement Ordered on a custom BPP, or order problems pile on | Section 3.2 · the second row of the Section 11 sandbox |
the classic beginner mistake with these long stack traces is to grind top-down. The right posture is to read only three lines: ① the outermost exception type (which step blew up); ② the first Error creating bean with name 'xxx' (which object failed); ③ the last Caused by: (the actual disease). The dozens of lines in between are the same kind of collateral and skipping them changes nothing.
That reading method needs a live case. Below is the most common step-eleven crash — do not check the table, click the frame you believe is guilty:
A teammate adds one repository class. Local startup now fails, the console scrolls for most of a screen, and he spends ten minutes reading from the top without understanding a line of it.
without looking back, compress the twelve steps into three time windows and say what problem each window solves.
Which step creates non-lazy singletons? How many business objects are in singletonObjects before that step?
Which interface do you implement to change "which beans exist", and at which step? Which one changes "what an object looks like", and when does it take effect?
What goes wrong if a custom BeanPostProcessor injects an ordinary bean? Which log line is its fingerprint?
When step 11 throws, where do the beans already built go? Why is it "all or nothing"?
one to four lay the foundation (environment, factory, built-ins), five and six set the rules (redraw plans, train staff), seven to ten ready the equipment (messages, broadcast, empty hook, listeners), eleven is where the food comes out (pre-instantiation), twelve cuts the ribbon (publishes the event) — and opening the doors to customers is the Runner, already outside refresh.
refresh() is not a black box but twelve clearly-scoped steps. Remember three groupings — steps 1–4 prepare the environment and factory, steps 5–6 process definitions and register post-processors, steps 7–12 ready the infrastructure and pre-instantiate singletons; remember two windows — BeanFactoryPostProcessor edits definitions (step 5), BeanPostProcessor edits instances (after step 6); and remember one finish line — step 12 publishes ContextRefreshedEvent. Once you can read these twelve steps, startup holds no more secrets.