How do you write and register a custom FailureAnalyzer for your own startup exception?
answer
- extends AbstractFailureAnalyzer<MyException>
- return new FailureAnalysis(description, action, cause)
- register in META-INF/spring.factories
- key = org.springframework.boot.diagnostics.FailureAnalyzer
- NOT a bean, NOT the .imports file
basics
~10 sExtend AbstractFailureAnalyzer<YourException>, override analyze() to return a FailureAnalysis with a description and action, then register the class name in META-INF/spring.factories under the org.springframework.boot.diagnostics.FailureAnalyzer key.
solid answer
~40 sCreate a class extending `AbstractFailureAnalyzer<T>` where `T` is your exception type, and override `protected FailureAnalysis analyze(Throwable rootFailure, T cause)` to return a `new FailureAnalysis(description, action, cause)`. The description says what broke; the action gives concrete fix steps. Then register it — crucially, **not** as a `@Bean` (the context is broken when it runs) — but by listing the fully-qualified class name in `src/main/resources/META-INF/spring.factories` under the key `org.springframework.boot.diagnostics.FailureAnalyzer`. Spring Boot loads it via `SpringFactoriesLoader` at startup, so it's available even though no application context exists yet. If your analyzer needs the `Environment` or `BeanFactory` (e.g. to read a property in the message), implement `EnvironmentAware` / `BeanFactoryAware` and Boot will inject them before analysis.
go deeper
Know it involves extending a base class and returning description+action.
Write the AbstractFailureAnalyzer subclass and know the spring.factories key.
Nail the spring.factories-not-imports-not-bean registration and EnvironmentAware/BeanFactoryAware injection.
Design a diagnostics story for a library/starter: which exceptions to analyze, message quality, and packaging analyzers in the starter's spring.factories.
## Step 1 — the analyzer class Extend `AbstractFailureAnalyzer<T>` typed to the exception you throw during startup: ```java package com.example.diagnostics; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; public class MissingApiKeyFailureAnalyzer extends AbstractFailureAnalyzer<MissingApiKeyException> { @Override protected FailureAnalysis analyze(Throwable rootFailure, MissingApiKeyException cause) { String description = "No API key was configured for the payment client."; String action = "Set 'app.payment.api-key' in application.yml, " + "or provide the APP_PAYMENT_API_KEY environment variable."; return new FailureAnalysis(description, action, cause); } } ``` Throw `MissingApiKeyException` from wherever your bean initialization validates config; the wrapper exceptions around it don't matter because `AbstractFailureAnalyzer` unwraps the chain to find your type. ## Step 2 — register it (the part people get wrong) FailureAnalyzers are discovered via `SpringFactoriesLoader`, so you list them in **`src/main/resources/META-INF/spring.factories`**: ```properties org.springframework.boot.diagnostics.FailureAnalyzer=\ com.example.diagnostics.MissingApiKeyFailureAnalyzer ``` Multiple analyzers are comma-separated (with `\` line continuations). **Do not** declare the analyzer as a `@Bean` or `@Component` — it would never be picked up for its purpose, because when analysis runs the application context has already failed to start, so there are no beans to consult. ### Why not the `.imports` file? Spring Boot 2.7/3.x moved **auto-configuration** registration out of `spring.factories` into `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`. That change applies **only to auto-configuration classes**. `FailureAnalyzer` registration still lives in `spring.factories` under its own key. Putting an analyzer in an `.imports` file will silently do nothing. ## Step 3 — (optional) access Environment / BeanFactory Because there's no context, you can't `@Autowired` anything. Instead implement Boot's aware interfaces and the diagnostics infrastructure injects them before calling `analyze`: ```java public class MissingApiKeyFailureAnalyzer extends AbstractFailureAnalyzer<MissingApiKeyException> implements org.springframework.context.EnvironmentAware { private org.springframework.core.env.Environment environment; @Override public void setEnvironment(org.springframework.core.env.Environment e) { this.environment = e; } // use this.environment inside analyze(...) to build a smarter action } ``` `BeanFactoryAware` works the same way. Only `Environment` and `BeanFactory` injection are supported. ## Testing You can unit-test the analyzer directly by constructing your exception and asserting on the returned `FailureAnalysis.getDescription()` / `getAction()` — no Spring context needed. ## Gotchas recap - Register in `spring.factories`, NOT `.imports`, NOT as a bean. - Type the generic to your real exception, not to a wrapper. - Return `null` from `analyze` if, on inspection, you decide it isn't actually your case.
- Why can't you register the FailureAnalyzer as a @Bean or @Component?Analyzers run when the application context has already failed to start, so there are no live beans to look up. Boot instantiates them directly via SpringFactoriesLoader from spring.factories, independent of the context.
- Your analyzer needs to read a property to build a better Action message. How do you get the Environment?Implement EnvironmentAware (or BeanFactoryAware for the bean factory). The diagnostics infrastructure injects them before calling analyze(). You cannot use @Autowired because no context exists.
saying these in an interview costs you the question
- Registering the analyzer as a @Bean/@Component
- Putting it in the AutoConfiguration.imports file
- Trying to @Autowired dependencies into the analyzer
- Typing AbstractFailureAnalyzer to a wrapper exception instead of the real cause