Built from the Spring Boot topic bank and every Boot question you've missed across nine exam papers, organised by concept. Spring Boot is now your weakest section at 56%, and this page explains why in one number: Actuator accounts for 29 of the 66 — 44%. It has cost you marks in four consecutive papers, it is almost entirely recall, and two tables cover most of it.
One distinction has cost you marks in four consecutive papers:
enabled versus exposed. It appeared as "is /actuator/health exposed via JMX?" (#6),
"which endpoints are provided out of the box?" (#6), "is info enabled by default?" (#9), and
"is loggers exposed over HTTP?" (#9). It is three sentences. They are printed below.
Two independent switches. Nearly a third of your Actuator misses are this one confusion.
| Enabled — does the endpoint exist? | Exposed — can you reach it? | |
|---|---|---|
| Property | management.endpoint.<id>.enabledglobally management.endpoints.enabled-by-default |
management.endpoints.web.exposure.includemanagement.endpoints.jmx.exposure.include |
| Default | everything except shutdown |
HTTP: health onlyJMX: * |
| Note the key | has no web/jmx segment |
always has web or jmx |
The three sentences:
(1) Everything is enabled except shutdown.
(2) Over HTTP only health is exposed; over JMX everything is.
(3) The exposure key contains web or jmx; the enablement key does not.
One bank question says HTTP exposes "health and info" by
default. That was true in Boot 2.0–2.4. Boot 2.5 removed info from the default
exposure — from 2.5 onward it is health only. Your exam targets Boot 2.5, so
health only is the answer. If a question offers both, check whether it names a version.
And the JMX layer beneath it: since Boot 2.2
JMX registration is itself off by default (spring.jmx.enabled=false). So the exposure list is
*, but nothing is registered until JMX is switched on. Two layers, two different switches — questions
usually ask about the exposure list.
Enough to recognise a fabricated name on sight, which is where several of these marks went.
| Endpoint | Enabled | HTTP by default | JMX | What it does |
|---|---|---|---|---|
health | yes | yes | yes | application + dependency health |
info | yes | no (2.5+) | yes | arbitrary app / build / git metadata |
loggers | yes | no | yes | read AND write log levels at runtime |
metrics | yes | no | yes | Micrometer meters |
env | yes | no | yes | raw ConfigurableEnvironment property sources |
configprops | yes | no | yes | bound @ConfigurationProperties values |
mappings | yes | no | yes | every @RequestMapping URL |
beans, conditions, scheduledtasks, caches, httptrace, auditevents | yes | no | yes | the rest of the technology-agnostic set |
threaddump | yes | no | yes | thread dump — not web-only |
heapdump | yes | no | NO | web-only — binary hprof file |
logfile | yes | no | NO | web-only — needs logging.file.name |
shutdown | NO | no | no | the only disabled endpoint |
Only two endpoints are web-only: heapdump and
logfile (plus jolokia if it's on the classpath). Both serve a
file or stream, which JMX has no way to carry — that's the reason, and it makes the pair easy to
remember. threaddump is the trap: it returns JSON, so JMX handles it fine.
Eight of your misses are health. This is all of it.
| Status | HTTP code | Meaning |
|---|---|---|
UP | 200 | healthy |
DOWN | 503 | unhealthy |
OUT_OF_SERVICE | 503 | deliberately withdrawn — not 500 |
UNKNOWN | 200 | can't tell, but still serving |
Aggregation is worst-wins, in this precedence order:
DOWN > OUT_OF_SERVICE > UP > UNKNOWN. So
UP + OUT_OF_SERVICE aggregates to OUT_OF_SERVICE, and a single
indicator being DOWN takes the whole endpoint to 503.
| Property | Values | Default |
|---|---|---|
management.endpoint.health.show-details | never · when-authorized · always | never |
management.endpoint.health.show-components | same three | follows show-details |
management.endpoint.health.roles | role names | empty |
management.endpoint.health.status.http-mapping.* | status → code | the table above |
Built-in indicators are named after a technology:
DiskSpace (always on), Ping (always on), then DataSource,
Redis, Mongo, Elasticsearch, Rabbit, Cassandra,
Mail, Ldap — each registered when its client is on the classpath.
ConnectionHealthIndicator and ApplicationHealthIndicator do not exist —
"connection" and "application" are concepts, not technologies.
The single most repeated topic in the whole series. Master tables 1 and 2 answer every one of these.
shutdown; only health is exposed over HTTP/actuator/health exposed via JMX?" you said
No (#6). "Is info enabled by default?" you took "not enabled" (#9). "Can
loggers levels be changed only via HTTP?" you said yes (#9).
Every one is answered by the same two-switch model.Why the two switches exist: enabled asks whether the endpoint bean is created at all; exposed asks whether a transport publishes it. An endpoint can exist and be unreachable — which is the default state of nearly all of them, and the safe default for production.
# expose more over HTTP
management.endpoints.web.exposure.include=health,info,metrics,loggers
management.endpoints.web.exposure.include=* # everything
# disable an endpoint entirely (it stops existing)
management.endpoint.env.enabled=false
# enable the one that ships disabled
management.endpoint.shutdown.enabled=true # still needs exposing separately!
That last line is the point of the distinction: enabling shutdown does not expose
it. You need both. And include=* will expose it once enabled — which is why Actuator should sit
behind Spring Security or on a separate management.server.port in production.
shutdown). Exposed = can you reach it (HTTP: health only · JMX: all). The exposure key has web/jmx in it./actuator, over exactly two transports: HTTP and JMX/bootactuator and /endpoint/actuator —
two invented paths — while /actuator sat unticked. #6: you ticked ICMP as a transport.| Property | Default | Moves |
|---|---|---|
management.endpoints.web.base-path | /actuator | the prefix for all endpoints |
management.endpoints.web.path-mapping.<id> | the endpoint id | renames one |
management.server.port | the app port | Actuator onto its own port |
management.server.base-path | — | context path on that separate port |
The transport rule is self-checking: every Actuator property path contains either
web or jmx. If an option names any other transport — TCP/IP, ICMP, named pipes — it is
invented. There is no ActuatorController class either; everything about where Actuator lives
is a property, never code and never the build file.
/actuator. Two transports: web and jmx. Nothing prefixed "boot", nothing nested under "endpoint".windows-version. Bank: you got the web-only question wrong
by including threaddump and/or loggers.
Only heapdump and logfile are web-only.Why exactly those two: JMX carries structured attributes and operations, not
binary streams. heapdump returns an hprof file and logfile returns a text
stream — neither fits down a JMX pipe. Everything that returns JSON, including
threaddump, works over both.
Two extra facts on the roster: logfile 404s unless logging.file.name
or logging.file.path is set — "provided" is not the same as "available"; and there is no
/off endpoint — shutting down is /shutdown (paper #3).
heapdump + logfile (a file and a stream). threaddump is JSON, so JMX handles it.shutdown and full exposureshutdown is the only disabled endpoint · include=* needs the webshutdown doesn't expose it. #5: you under-selected on
"expose all endpoints", rejecting the invalid keys correctly but missing one valid one.| Property | Valid? |
|---|---|
management.endpoints.web.exposure.include=* | yes — note the web |
management.endpoints.enabled-by-default=true | yes — note there's no web |
management.endpoints.exposure.include=* | no — missing web |
management.endpoints.web.enabled-by-default=true | no — extra web |
Read the key segment by segment. The two valid keys differ in exactly the place the two invalid ones get wrong — which is the entire question.
exposure HAS web. enabled-by-default has NO web. Only shutdown ships disabled.configprops vs env · mappings · what Actuator is for| Endpoint | Shows | Confused with |
|---|---|---|
configprops | the bound values on @ConfigurationProperties beans, by prefix | env |
env | the raw property sources — files, env vars, args | configprops |
mappings | every @RequestMapping URL in the app | beans |
auditevents | recorded audit events (AuditEventRepository) | — |
httptrace | the last ~100 request/response exchanges | — |
flyway / liquibase | applied database migrations | — |
configprops is the one to understand: it shows configuration after binding,
so it's how you verify relaxed naming and type conversion actually produced the values you expected. Sensitive
keys (*password*, *secret*, *key*, *token*) are sanitised to
******.
And what Actuator provides overall, which is its own question: health monitoring,
metrics (via Micrometer), audit events, app info, log-level management, thread dumps, HTTP traces. It does
not provide dynamic property management — changing configuration at runtime is
@RefreshScope from Spring Cloud, not Actuator.
env = raw sources · configprops = bound values · mappings = URLs. Actuator monitors; it doesn't reconfigure.Master table 3 covers all eight. The two that recur are the HTTP code mapping and the aggregation order.
DOWN and OUT_OF_SERVICE both map to 503OUT_OF_SERVICE → 500 and UNKNOWN → 404.
Neither exists — the mapping only ever produces 200 or 503.The logic is binary, which makes it easy: is the app able to serve traffic? If yes → 200; if
no → 503. OUT_OF_SERVICE means "deliberately withdrawn", which is still *not serving*, hence 503 —
the same code as DOWN. UNKNOWN means "can't tell", and Actuator errs toward
available → 200.
# override if you really want to
management.endpoint.health.status.http-mapping.DOWN=500
management.endpoint.health.status.http-mapping.OUT_OF_SERVICE=404
DOWN > OUT_OF_SERVICE > UP > UNKNOWNUP + OUT_OF_SERVICE aggregating to something other
than OUT_OF_SERVICE. The overall status is the worst of the
components, never an average or the first.The endpoint's top-level status is computed by StatusAggregator from
every registered indicator. One DOWN anywhere takes the whole endpoint down — which is exactly what
you want a load balancer to see.
{
"status": "DOWN", ← the aggregate: worst wins
"components": {
"db": { "status": "UP" },
"diskSpace": { "status": "UP" },
"redis": { "status": "DOWN" } ← one bad apple
}
}
Customise the precedence with
management.endpoint.health.status.order. And note the overall status is not an
indicator — there is no ApplicationHealthIndicator; it's the aggregate.
management.endpoint.health.show-details = never (default) · when-authorized · alwaysHealthEndpointWebExtension.With the default, /actuator/health returns only {"status":"UP"} — the
components block is invisible. That's deliberate: indicator details can leak database names, hosts
and disk paths.
management.endpoint.health.show-details=when-authorized
management.endpoint.health.roles=ADMIN,ACTUATOR
Don't confuse the two kinds of customisation: show-details controls
visibility of what's already there; implementing HealthIndicator adds a new
component. The bank's distractors deliberately offered the second as an answer to the first.
Also worth knowing: health groups (Boot 2.2+) let you build separate probes —
management.endpoint.health.group.readiness.include=db,redis — and Boot 2.3+ ships
/actuator/health/liveness and /readiness as first-class Kubernetes probes.
show-details defaults to never. It controls visibility; HealthIndicator adds components.ConnectionHealthIndicator does not existConnectionHealthIndicator.
You correctly rejected ApplicationHealthIndicator, which is the same kind of
fake.Every built-in indicator is named after something you can go and ping:
DiskSpace and Ping are always registered; DataSource, Redis,
Mongo, Elasticsearch, Rabbit, Cassandra, Neo4j,
Mail, Jms and Ldap appear when that client is on the classpath.
"Connection" and "Application" name concepts, not technologies — there is no single "the connection" to check, and application health is the aggregate rather than a component. Writing your own shows why the convention exists:
@Component
public class PaymentGatewayHealthIndicator implements HealthIndicator {
public Health health() {
try { gateway.ping(); return Health.up().withDetail("region", gateway.region()).build(); }
catch (Exception e) { return Health.down(e).build(); }
}
}
// appears in the JSON as "paymentGateway" — the bean name minus the suffix
Counter · Gauge · Timer · DistributionSummaryGauge behaving cumulatively or updating itself. #4: you missed that
Timer is what measures how long a method takes.
Each meter answers a different question.| Meter | Answers | Behaviour |
|---|---|---|
Counter | "how many times?" | monotonically increasing; you call increment() |
Gauge | "what is it right now?" | polls a value you maintain — not cumulative, never self-updating |
Timer | "how long, and how often?" | records count AND total time together |
DistributionSummary | "what's the spread?" | samples + distribution (percentiles, histograms) |
The Gauge subtlety that caught you: a gauge holds a weak
reference to an object it samples. It doesn't accumulate and it doesn't push — Micrometer reads the current
value when the registry is scraped. That's why gauges suit queue depth or cache size, and are wrong for counting
events.
Counter c = registry.counter("orders.placed"); c.increment();
Gauge.builder("queue.depth", queue, Queue::size).register(registry); // polled
Timer t = registry.timer("db.query"); t.record(() -> repo.findAll());
Two more from these questions: tags are optional, not mandatory; and Actuator does not require an external monitoring system — Prometheus/Grafana are optional integrations via a Micrometer registry.
?tag=KEY:VALUE — one =, then a :tag:KEY:VALUE and tag=KEY=VALUE.
The first drops the query-parameter form; the second is unparseable because the second
= is ambiguous.GET /actuator/metrics # list metric NAMES
GET /actuator/metrics/http.server.requests # one metric + its available tags
GET /actuator/metrics/http.server.requests?tag=uri:/api/users&tag=status:200
It's an ordinary query parameter whose value is a colon-joined pair; repeat the parameter to AND several filters. Hitting the metric without tags is how you discover which tag keys exist.
?tag=KEY:VALUE. Repeat the parameter to add filters. /actuator/metrics alone lists names.@LocalManagementPort — or @Value("${local.management.port}")When management.server.port puts Actuator on its own port, a test needs to know which
port that turned out to be:
| Annotation | Injects |
|---|---|
@LocalServerPort | the application port |
@LocalManagementPort | the Actuator management port |
Both are just aliases for @Value on a property Boot publishes —
${local.server.port} and ${local.management.port} — which is why the
@Value form is equally correct.
local.*.port property.@Conditional — and it backs off, it never merges@Conditional is conditional bean registration — and it is
the engine the whole mechanism runs on.The pipeline, end to end:
| Step | What happens |
|---|---|
| 1 | @EnableAutoConfiguration (inside @SpringBootApplication) switches the mechanism on |
| 2 | AutoConfigurationImportSelector reads the candidate list from META-INF/spring.factories (≤2.6) or …AutoConfiguration.imports (2.7+) |
| 3 | Candidates are ordered — alphabetically, then @AutoConfigureOrder, then @AutoConfigureBefore/After |
| 4 | Each is evaluated against its @Conditional* annotations, in order |
| 5 | Survivors register their beans |
| Condition | Registers when |
|---|---|
@ConditionalOnClass / @ConditionalOnMissingClass | a class is / isn't on the classpath |
@ConditionalOnMissingBean | no such bean exists — this is "backing off" |
@ConditionalOnBean | a bean of that type does exist |
@ConditionalOnProperty | a property has a given value |
@ConditionalOnExpression | a SpEL expression is true |
@ConditionalOnWebApplication | running in a web context |
Why order matters (and why step 3 exists): conditions are evaluated against the
beans registered so far, not the final context. So an auto-config running early is more likely to win its
@ConditionalOnMissingBean. User beans are always registered before any auto-configuration,
which is what makes back-off work at all.
And the fact that ties it together: @Profile is itself a
@Conditional — @Conditional(ProfileCondition.class). So "environment-based loading"
is one use of the mechanism, not its definition.
@ConditionalOnMissingBean in practiceThis is the single most important behaviour in Spring Boot, and it's worth stating precisely:
Boot does not merge or override — it steps aside. The condition is checked, fails, and the bean definition
is never registered. There is no conflict to resolve, no @Primary, no exception.
@Bean
@ConditionalOnMissingBean(CacheManager.class)
public CacheManager cacheManager() { return new ConcurrentMapCacheManager("default"); }
// user defines their own CacheManager → this method is never called
You've now seen the same mechanism in three guises: declaring a DataSource disables
DataSourceAutoConfiguration; declaring a SecurityFilterChain disables Boot's default
chain (review #7); declaring a CacheManager disables this one. Same rule every time.
@AutoConfigureBefore(name=…) with a missing
class. #5: you answered "use @ContextConfiguration" for excluding auto-config in a test.| Where | How you exclude / customise |
|---|---|
| Best default | just define your own bean — the auto-config backs off |
| Application, at compile time | @SpringBootApplication(exclude = X.class) · excludeName = "…" for optional classes |
| Application, per environment | spring.autoconfigure.exclude=… (a property, so profile-able) |
Test slice (@WebMvcTest, @DataJpaTest) | @ImportAutoConfiguration(exclude = …) or the slice's excludeAutoConfiguration |
| Tuning behaviour | properties (spring.datasource.* etc.) |
The test-slice row is its own trap: slices are meta-annotated with
@ImportAutoConfiguration, so exclusions on your application class — and
spring.autoconfigure.exclude — have no effect on them. There is no
@DisableAutoConfiguration.
And the ordering annotations, from paper #8:
@AutoConfigureBefore/@AutoConfigureAfter are hints, silently ignored if the
referenced class is absent — which is why the name = "…" String form exists at all. Contrast
@DependsOn, which orders beans and throws on a missing reference.
Two more from the bank: don't subclass Boot's auto-configuration classes (write your own conditional config instead), and auto-configuration classes should not component-scan — they declare beans explicitly so that library consumers get predictable behaviour.
exclude / spring.autoconfigure.exclude. Slice = @ImportAutoConfiguration(exclude). Ordering hints are ignored if absent; @DependsOn throws.@Configuration — or, from Boot 2.7, @AutoConfiguration@EnableAutoConfiguration.
That goes on your application, to say "go and find them" — never on the auto-config
class itself.The direction is the whole point: @EnableAutoConfiguration is the consumer;
auto-configuration classes are the producers, and they're found via a registration file, not an
annotation:
@AutoConfiguration // Boot 2.7+ (was plain @Configuration)
@ConditionalOnClass(MyClient.class)
@EnableConfigurationProperties(MyProps.class)
public class MyAutoConfiguration {
@Bean @ConditionalOnMissingBean
MyClient myClient(MyProps p) { return new MyClient(p.getUrl()); }
}
@ConditionalConfiguration and @ConfigurationCondition were the invented
distractors — worth noting, given how many marks fabricated names have cost you across these papers.
Main-Class + Start-Class| Entry | Original (thin) jar | Boot fat jar |
|---|---|---|
Main-Class | your main, or absent | org.springframework.boot.loader.JarLauncher |
Start-Class | absent | your @SpringBootApplication class |
| Dependencies | absent | BOOT-INF/lib/*.jar — nested, not shaded |
| Your classes | at the root | BOOT-INF/classes/ |
Runs with java -jar | no | yes |
Why the custom launcher exists: the JDK's class loader cannot read a jar nested
inside another jar. So Boot ships its own LaunchedURLClassLoader inside the archive, points
Main-Class at JarLauncher, and records your real main class under the custom
Start-Class header. JarLauncher builds the class loader, reads
Start-Class, and invokes it.
Consequence worth knowing: a fat jar can't be put on another app's classpath with
-cp, because those nested jars are invisible to a normal class loader. That's what the
*-original.jar beside it is for. And a fat jar is not a shaded/uber jar — dependencies aren't
merged, so signatures and META-INF/services files survive intact.
SpringBootApplication.shutdown(), which doesn't exist.
#3: you missed that Boot registers the JVM shutdown hook for you.| Method | Does |
|---|---|
SpringApplication.exit(context, exitCodeGenerators…) | closes the context, returns an exit code |
AbstractApplicationContext.close() | closes it, destroying singletons |
registerShutdownHook() | closes it on JVM exit — Boot calls this automatically |
/actuator/shutdown | HTTP-triggered — disabled by default |
SpringBootApplication.shutdown() | does not exist |
The practical upshot: in a Boot app, @PreDestroy and DisposableBean
callbacks fire on Ctrl-C or SIGTERM without you writing anything, because
SpringApplication.run() registers the hook. In plain Spring you'd call
registerShutdownHook() yourself.
exit(), close(), registerShutdownHook() — and Boot already does the third. There is no SpringBootApplication.shutdown().| Boot provides | Boot does NOT provide |
|---|---|
| Auto-configuration of Spring and third-party libraries | Java code generation (that's Spring Initializr, once, at project creation) |
| Starter POMs with curated transitive dependencies | Vendor JDBC drivers (Oracle, SQL Server — licensed artifacts) |
| Embedded Tomcat, Jetty, Undertow (and Netty for WebFlux) | An application server |
| Actuator: health, metrics, monitoring | Dynamic property reconfiguration (that's Spring Cloud) |
| Sensible defaults + externalised configuration | Its own DI container — that's Spring Framework |
The four embedded containers are worth memorising as a set: Tomcat
(default), Jetty, Undertow for servlet stacks, and Netty for reactive (WebFlux). Switching
means excluding spring-boot-starter-tomcat and adding the replacement — and note that swapping
Tomcat for Jetty still gives you an embedded server. To use an external container you package a
WAR and extend SpringBootServletInitializer.
BasicErrorControllerOne controller, two representations, chosen by content negotiation:
| Client sends | Gets |
|---|---|
Accept: text/html (a browser) | the Whitelabel Error Page |
| anything else (a REST client) | a JSON error body: timestamp, status, error, path |
server.error.include-message=always # messages are hidden by default (Boot 2.3+)
server.error.include-stacktrace=on_param
server.error.whitelabel.enabled=false # turn the HTML page off
Customise properly with @ControllerAdvice + @ExceptionHandler, or by
implementing ErrorController. And note @ResponseStatus on an exception class is the
lightweight way to map one exception to one status.
The four default locations, lowest precedence first — the rule is
external beats packaged, and /config beats the directory above it:
| Order | Location | Inside or outside the jar |
|---|---|---|
| 1 (lowest) | classpath:/ | packaged |
| 2 | classpath:/config/ | packaged |
| 3 | file:./ — the working directory | external |
| 4 (highest) | file:./config/ | external |
At each location Boot loads application.{properties,yml} first, then
application-{profile}.{properties,yml} for every active profile — profile-specific always beats
plain at the same location.
The wider precedence chain, which is the other half of these questions:
| Priority | Source |
|---|---|
| highest | command-line arguments (--server.port=8081) |
| ↑ | SPRING_APPLICATION_JSON |
| ↑ | OS environment variables |
| ↑ | Java system properties (-D) |
| ↑ | application-{profile} files |
| lowest | application.properties / .yml |
Supported formats: .properties, .yml/.yaml, and
properties-format .xml. Not .conf, not .ini, not
.txt. And "XML" as a general config source is wrong — XML configures beans in legacy Spring;
it is not a property source. Anything else needs a custom PropertySourceLoader.
Two properties worth distinguishing: spring.config.location replaces the search
path; spring.config.additional-location adds to it. Use the second unless you really mean to
lose the defaults.
@ConfigurationProperties and @PropertySource@PropertySource takes a file, @ConfigurationProperties takes a prefix@PropertySource was needed for application.yaml.
It isn't — Boot loads that automatically.@Component
@ConfigurationProperties(prefix = "foo") // a PREFIX, not a file
@PropertySource("classpath:foo.properties") // a FILE, not a prefix
public class FooProperties {
private String name; // binds foo.name
private int timeout; // binds foo.timeout
// getters + setters
}
@ConfigurationProperties | @PropertySource | |
|---|---|---|
| Takes | a prefix string | a resource location |
| Does | binds Environment properties onto fields | adds a file to the Environment |
| Placement | class or @Bean method | class |
| YAML? | yes (any source) | no — .properties only |
Two facts that follow: @PropertySource does not support YAML, which is a stock
question; and you don't need it for application.yml/application.properties at all —
those are loaded by Boot's config-data machinery before any annotation is processed.
If the class isn't component-scanned, register it with
@EnableConfigurationProperties(FooProperties.class) or @ConfigurationPropertiesScan.
@ConfigurationProperties also gives you relaxed binding (foo.my-name,
FOO_MYNAME, foo.myName all bind) and validation with @Validated —
neither of which @Value offers.
-spring variantlogging.properties, logback-spring.xml and
log4j2-spring.xml in both directions. The mapping is one-to-one.| Backend | Native file | Spring-aware file | Starter |
|---|---|---|---|
| Logback (default) | logback.xml | logback-spring.xml | spring-boot-starter-logging |
| Log4j2 | log4j2.xml | log4j2-spring.xml | spring-boot-starter-log4j2 |
| JUL | logging.properties | none | fallback only |
Why the -spring variants matter: a plain logback.xml is
read by Logback before the Spring Environment exists, so <springProfile>
and <springProperty> tags silently don't work. The -spring file is loaded by Boot
afterwards, so they do. JUL has no Spring variant at all — it has no notion of profiles.
Switching backends means excluding spring-boot-starter-logging and adding
spring-boot-starter-log4j2. Without the exclusion both SLF4J bindings are on the classpath and
Logback silently wins.
| Levels | Configurable aspects |
|---|---|
OFF | Level — logging.level.<logger> |
FATAL (Log4j2 only) | Destination — logging.file.name / .path |
ERROR | Pattern — logging.pattern.console / .file |
WARN | Rotation — logging.logback.rollingpolicy.* |
INFO (root default) | Colour — spring.output.ansi.enabled |
DEBUG | Groups — logging.group.<name> |
TRACE | No masking / anonymisation |
Invented levels to recognise: EXCEPTION, CRITICAL, VERBOSE,
NOTICE. (SEVERE is real but belongs to java.util.logging, not SLF4J.)
The two-layer architecture, which is #9's question: Spring's own code logs through
Commons Logging (JCL) — the API it compiles against — while Logback is the default
implementation that produces output. Since Spring 5, spring-jcl reimplements the JCL API
and delegates to SLF4J/Log4j2. In your code, use SLF4J.
One rotation trap: logging.logback.rollingpolicy.* is silently ignored unless
logging.file.name is set — no file means no rolling appender.
spring.factories & component scanning 6 missesspring.factories three timesMETA-INF/spring.factories — anything that must exist before the ApplicationContext doesWEB-INF/ and META-INF/config/ for its
location. #9: you under-selected when all four options were correct. Bank: you ticked "Spring
Beans" and "FactoryBean objects".The organising idea: everything registered here runs before there is a container to look things up in. That's why it's a file and not a bean — and why "regular Spring beans" is always the wrong answer.
| Key | Registers | Runs |
|---|---|---|
EnableAutoConfiguration | auto-configuration classes | during context creation |
AutoConfigurationImportFilter | filters trimming candidates early | before conditions |
ApplicationContextInitializer | context customisation | before refresh |
ApplicationListener | startup event listeners | before refresh |
EnvironmentPostProcessor | Environment mutation | before the context exists |
FailureAnalyzer | friendly startup-failure messages | on failure |
SpringApplicationRunListener | run-lifecycle hooks | throughout |
# META-INF/spring.factories ← root of META-INF, no sub-folder
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.acme.MissingApiKeyFailureAnalyzer,\
com.acme.OtherAnalyzer
SpringFactoriesLoader scans every JAR for that exact path and merges
the entries — which is how a third-party starter contributes behaviour simply by being on the classpath. Missing a
trailing \ silently truncates the list.
The version change: Boot 2.7 moved auto-configuration registration only
to META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (one FQN per
line, no key, no backslashes), and Boot 3 removed the legacy key entirely. Every other key stayed in
spring.factories.
META-INF. Interface FQN = implementation FQNs, merged across JARs. Never regular beans. Auto-config moved out in 2.7.@SpringBootApplication does@SpringBootApplication's purpose. Bank: you picked hard-coded package names like
com.spring.boot.@SpringBootApplication is three annotations, and the scan root comes from the class's
own package:
| Meta-annotation | Effect |
|---|---|
@SpringBootConfiguration | a @Configuration — and the marker Boot's test bootstrapper searches for |
@EnableAutoConfiguration | loads auto-configuration candidates |
@ComponentScan | scans this class's package and every sub-package |
com/example/myapp/
├── MyApplication.java @SpringBootApplication ← the scan root
├── web/UserController.java ← found
├── service/UserService.java ← found
└── repository/UserRepository.java ← found
com/other/Thing.java ← NOT found (sibling package)
The same package is also registered as the auto-configuration package, which is
what @EntityScan, @EnableJpaRepositories and friends fall back on when their own
basePackages are absent. So the main class's location silently drives several mechanisms at once.
Two traps: putting the main class in the default package makes it scan the entire classpath
(including libraries) and usually fails or crawls; and @SpringBootTest without
classes walks up from the test's package looking for
@SpringBootConfiguration — so a misplaced test finds the wrong config, or none.
Override with @SpringBootApplication(scanBasePackages = "…"), which adjusts both the
component scan and the auto-configuration package.
shutdown; HTTP exposes health only, JMX exposes *exposure keys contain web/jmx; enabled-by-default does notinfo lost default HTTP exposure in Boot 2.5 (it was exposed in 2.0–2.4)spring.jmx.enabled=true to switch it on/actuator via management.endpoints.web.base-path; path-mapping.<id> renames one; management.server.port moves them allActuatorControllerheapdump and logfile (a file and a stream). threaddump is JSON, so JMX carries itlogfile 404s unless logging.file.name/.path is setenv = raw property sources · configprops = bound values · mappings = every URLshow-details defaults to never; when-authorized pairs with health.rolesConnection, no Application?tag=KEY:VALUE, repeated to AND filters. Tags are optional@LocalManagementPort for the Actuator port; @LocalServerPort for the app port@Conditional gates → survivors register. User beans always first@ConditionalOnMissingBean = backing off — Boot never merges, overrides or throwsexclude/spring.autoconfigure.exclude · slice = @ImportAutoConfiguration(exclude). No @DisableAutoConfiguration@AutoConfigureBefore/After are hints, ignored if absent; @AutoConfigureOrder is absolute; @DependsOn orders beans and throws@Configuration (or @AutoConfiguration in 2.7+) — never @EnableAutoConfiguration, and they never component-scanMain-Class=JarLauncher, Start-Class=yours, deps nested in BOOT-INF/lib, bigger, executable, not shadedSpringApplication.exit() · close() · registerShutdownHook() (Boot does this for you). No SpringBootApplication.shutdown()BasicErrorController serves Whitelabel HTML to browsers, JSON to everyone else; messages hidden by default/config → ./ → ./config, each beating the last; profile-specific beats plainapplication.properties.properties, .yml/.yaml, properties-format .xml. Not .conf/.ini/.txt; XML bean config is not a property source@ConfigurationProperties = prefix, whole-object binding, relaxed + validated · @PropertySource = a .properties file, never YAMLlogback-spring.xml · Log4j2 → log4j2-spring.xml · JUL → logging.propertiesspring.factories = root of META-INF, merged across JARs, for anything running before the context. Auto-config moved out in 2.7@SpringBootApplication class's own package, downward. Never the default packagewindows-version, ConnectionHealthIndicator, server.timeout,
SpringBootApplication.shutdown(), @ConditionalConfiguration,
/bootactuator. Ask "have I seen this?" before ticking.
(5) Come back on a three-day cycle. Boot has swung 92% → 40% → 64% → 71% → 56% across five papers; the
sections that stick are the ones you revisit.