Topic revision clinic · Spring Boot · 66 misses

Spring Boot — the 66 you've got wrong

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.

29
Actuator (44%)
11
Auto-configuration
9
Packaging & lifecycle
56%
Latest Boot score
Where the 66 fall. One cluster dominates everything else.
Actuator — exposure & endpoints 14papers #1,2,3,5,6,7,8,9 + bank Actuator — health 8papers #1,6,8,9 + bank Actuator — metrics 7papers #3,4,6 + bank Auto-configuration 11papers #1,2,5,8,9 + bank Packaging, startup & shutdown 9papers #3,7,8 + bank External configuration 6papers #1,3,7 + bank Logging 5papers #6,9 + bank spring.factories 3papers #7,9 + bank Component scanning 3papers #3,6 + bank

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.

Master table 1 — enabled vs exposed

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>.enabled
globally management.endpoints.enabled-by-default
management.endpoints.web.exposure.include
management.endpoints.jmx.exposure.include
Default everything except shutdown HTTP: health only
JMX: *
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.

A version boundary your materials disagree on

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.

Master table 2 — the endpoint roster

Enough to recognise a fabricated name on sight, which is where several of these marks went.

EndpointEnabledHTTP by defaultJMXWhat it does
healthyesyesyesapplication + dependency health
infoyesno (2.5+)yesarbitrary app / build / git metadata
loggersyesnoyesread AND write log levels at runtime
metricsyesnoyesMicrometer meters
envyesnoyesraw ConfigurableEnvironment property sources
configpropsyesnoyesbound @ConfigurationProperties values
mappingsyesnoyesevery @RequestMapping URL
beans, conditions, scheduledtasks, caches, httptrace, auditeventsyesnoyesthe rest of the technology-agnostic set
threaddumpyesnoyesthread dump — not web-only
heapdumpyesnoNOweb-only — binary hprof file
logfileyesnoNOweb-only — needs logging.file.name
shutdownNOnonothe 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.

Master table 3 — health status, aggregation and HTTP codes

Eight of your misses are health. This is all of it.

StatusHTTP codeMeaning
UP200healthy
DOWN503unhealthy
OUT_OF_SERVICE503deliberately withdrawn — not 500
UNKNOWN200can'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.

PropertyValuesDefault
management.endpoint.health.show-detailsnever · when-authorized · alwaysnever
management.endpoint.health.show-componentssame threefollows show-details
management.endpoint.health.rolesrole namesempty
management.endpoint.health.status.http-mapping.*status → codethe 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.

Jump to a section

① Actuator — exposure & endpoints — 14 ② Actuator — health — 8 ③ Actuator — metrics — 7 ④ Auto-configuration — 11 ⑤ Packaging, startup & shutdown — 9 ⑥ External configuration — 6 ⑦ Logging — 5 ⑧ spring.factories & scanning — 6

① Actuator — exposure & endpoints 14 misses

The single most repeated topic in the whole series. Master tables 1 and 2 answer every one of these.

Papers #1, #2, #6, #9 · enabled vs exposed four consecutive papers

Everything is enabled except shutdown; only health is exposed over HTTP

The same confusion, four ways: "is /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.

Memory hook: Enabled = does it exist (all but shutdown). Exposed = can you reach it (HTTP: health only · JMX: all). The exposure key has web/jmx in it.
Papers #6, #7 · the base path and the transports twice

/actuator, over exactly two transports: HTTP and JMX

#7: you ticked /bootactuator and /endpoint/actuator — two invented paths — while /actuator sat unticked. #6: you ticked ICMP as a transport.
PropertyDefaultMoves
management.endpoints.web.base-path/actuatorthe prefix for all endpoints
management.endpoints.web.path-mapping.<id>the endpoint idrenames one
management.server.portthe app portActuator onto its own port
management.server.base-pathcontext 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.

Memory hook: /actuator. Two transports: web and jmx. Nothing prefixed "boot", nothing nested under "endpoint".
Paper #6 + bank · which endpoints exist invented names

Learn the roster well enough to spot a fake — and know the two web-only ones

#6: you ticked 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).

Memory hook: Web-only = heapdump + logfile (a file and a stream). threaddump is JSON, so JMX handles it.
Papers #1, #5 · shutdown and full exposure

shutdown is the only disabled endpoint · include=* needs the web

#1: you missed that enabling shutdown doesn't expose it. #5: you under-selected on "expose all endpoints", rejecting the invalid keys correctly but missing one valid one.
PropertyValid?
management.endpoints.web.exposure.include=*yes — note the web
management.endpoints.enabled-by-default=trueyes — note there's no web
management.endpoints.exposure.include=*no — missing web
management.endpoints.web.enabled-by-default=trueno — 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.

Memory hook: exposure HAS web. enabled-by-default has NO web. Only shutdown ships disabled.
Bank + paper #3 · other endpoints and their jobs

configprops vs env · mappings · what Actuator is for

Several questions turn on telling near-neighbours apart:
EndpointShowsConfused with
configpropsthe bound values on @ConfigurationProperties beans, by prefixenv
envthe raw property sources — files, env vars, argsconfigprops
mappingsevery @RequestMapping URL in the appbeans
auditeventsrecorded audit events (AuditEventRepository)
httptracethe last ~100 request/response exchanges
flyway / liquibaseapplied 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.

Memory hook: env = raw sources · configprops = bound values · mappings = URLs. Actuator monitors; it doesn't reconfigure.

② Actuator — health 8 misses

Master table 3 covers all eight. The two that recur are the HTTP code mapping and the aggregation order.

Papers #1, bank · status → HTTP code twice

DOWN and OUT_OF_SERVICE both map to 503

You've matched OUT_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
Memory hook: Only two codes come out: 200 (UP, UNKNOWN) and 503 (DOWN, OUT_OF_SERVICE). Never 500, never 404.
Paper #1 · aggregating several indicators

Worst wins: DOWN > OUT_OF_SERVICE > UP > UNKNOWN

You had UP + 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.

Memory hook: Worst status wins. One DOWN component ⇒ whole endpoint DOWN ⇒ HTTP 503.
Bank · showing detail

management.endpoint.health.show-details = never (default) · when-authorized · always

Your answer was one of the "you can't — write your own controller" options. Built-in support exists; a custom controller would duplicate HealthEndpointWebExtension.

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.

Memory hook: show-details defaults to never. It controls visibility; HealthIndicator adds components.
Paper #8 · built-in indicators invented name

Named after a technologyConnectionHealthIndicator does not exist

Your answer: you ticked ConnectionHealthIndicator. 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
Memory hook: Indicators are named after a pingable technology. DiskSpace and Ping always; the rest follow their client onto the classpath.

③ Actuator — metrics 7 misses

Papers #3, #4 + bank · the Micrometer meter types three times

Counter · Gauge · Timer · DistributionSummary

#3: you had Gauge behaving cumulatively or updating itself. #4: you missed that Timer is what measures how long a method takes. Each meter answers a different question.
MeterAnswersBehaviour
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.

Memory hook: Counter counts · Gauge samples a current value · Timer = count + duration · DistributionSummary = spread.
Papers #6, bank · filtering metrics by tag twice

?tag=KEY:VALUE — one =, then a :

You've ticked 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.

Memory hook: ?tag=KEY:VALUE. Repeat the parameter to add filters. /actuator/metrics alone lists names.
Paper #4 · the Actuator management port in tests

@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:

AnnotationInjects
@LocalServerPortthe application port
@LocalManagementPortthe 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.

Memory hook: @LocalServerPort = app · @LocalManagementPort = Actuator. Both wrap a local.*.port property.

④ Auto-configuration 11 misses

Papers #2, #9 + bank · how auto-configuration actually works three times

Registered candidates, each gated by @Conditional — and it backs off, it never merges

#9: you took "@Conditional is for environment-based config" and "for app run statements". @Conditional is conditional bean registration — and it is the engine the whole mechanism runs on.

The pipeline, end to end:

StepWhat happens
1@EnableAutoConfiguration (inside @SpringBootApplication) switches the mechanism on
2AutoConfigurationImportSelector reads the candidate list from META-INF/spring.factories (≤2.6) or …AutoConfiguration.imports (2.7+)
3Candidates are ordered — alphabetically, then @AutoConfigureOrder, then @AutoConfigureBefore/After
4Each is evaluated against its @Conditional* annotations, in order
5Survivors register their beans
ConditionRegisters when
@ConditionalOnClass / @ConditionalOnMissingClassa class is / isn't on the classpath
@ConditionalOnMissingBeanno such bean exists — this is "backing off"
@ConditionalOnBeana bean of that type does exist
@ConditionalOnPropertya property has a given value
@ConditionalOnExpressiona SpEL expression is true
@ConditionalOnWebApplicationrunning 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.

Memory hook: Candidate list → ordered → each gated by @Conditional → survivors register. @ConditionalOnMissingBean is how Boot backs off. User beans come first.
Bank Q28 · @ConditionalOnMissingBean in practice

Your bean wins; the auto-configured one is simply never created

Your answer covered "an exception is thrown", "both load with one marked primary", and "the conditional one always overrides". All three assume two beans exist. Only one ever does.

This 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.

Memory hook: Define your own bean and the auto-configuration never runs. Boot backs off — it doesn't merge, override or complain.
Papers #5, #8 + bank · customising and excluding several times

Four mechanisms, and the right one depends on where you are

#8: you ticked three wrong options on @AutoConfigureBefore(name=…) with a missing class. #5: you answered "use @ContextConfiguration" for excluding auto-config in a test.
WhereHow you exclude / customise
Best defaultjust define your own bean — the auto-config backs off
Application, at compile time@SpringBootApplication(exclude = X.class) · excludeName = "…" for optional classes
Application, per environmentspring.autoconfigure.exclude=… (a property, so profile-able)
Test slice (@WebMvcTest, @DataJpaTest)@ImportAutoConfiguration(exclude = …) or the slice's excludeAutoConfiguration
Tuning behaviourproperties (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.

Memory hook: App = exclude / spring.autoconfigure.exclude. Slice = @ImportAutoConfiguration(exclude). Ordering hints are ignored if absent; @DependsOn throws.
Bank Q20 · what annotates an auto-configuration class

@Configuration — or, from Boot 2.7, @AutoConfiguration

Your answer included @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.

Memory hook: @EnableAutoConfiguration consumes; @Configuration/@AutoConfiguration produces. Registration happens in a file, not an annotation.

⑤ Packaging, startup & shutdown 9 misses

Paper #3 + bank · fat jar vs original jar twice

Executable, bigger, nested dependencies, Main-Class + Start-Class

Your answers have included "the fat jar is smaller". It is strictly larger — it contains every runtime dependency.
EntryOriginal (thin) jarBoot fat jar
Main-Classyour main, or absentorg.springframework.boot.loader.JarLauncher
Start-Classabsentyour @SpringBootApplication class
DependenciesabsentBOOT-INF/lib/*.jarnested, not shaded
Your classesat the rootBOOT-INF/classes/
Runs with java -jarnoyes

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.

Memory hook: Fat jar = JarLauncher as Main-Class, your class as Start-Class, deps nested under BOOT-INF/lib. Bigger, executable, not shaded.
Paper #3 + bank · shutting the context down twice

Three real ways — and in Boot you normally do nothing

Bank: you ticked SpringBootApplication.shutdown(), which doesn't exist. #3: you missed that Boot registers the JVM shutdown hook for you.
MethodDoes
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/shutdownHTTP-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.

Memory hook: exit(), close(), registerShutdownHook() — and Boot already does the third. There is no SpringBootApplication.shutdown().
Papers #3, #8 + bank · what Boot provides, and what it doesn't

Starters, auto-configuration, embedded servers, Actuator — but no code generation and no database drivers

Bank: you ticked "Java code generation". #8: you ticked "provides drivers for all enterprise databases". Both claim Boot ships something it deliberately doesn't.
Boot providesBoot does NOT provide
Auto-configuration of Spring and third-party librariesJava code generation (that's Spring Initializr, once, at project creation)
Starter POMs with curated transitive dependenciesVendor JDBC drivers (Oracle, SQL Server — licensed artifacts)
Embedded Tomcat, Jetty, Undertow (and Netty for WebFlux)An application server
Actuator: health, metrics, monitoringDynamic property reconfiguration (that's Spring Cloud)
Sensible defaults + externalised configurationIts 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.

Memory hook: Starters + auto-config + embedded server + Actuator. Never code generation, never a vendor driver, never an app server.
Bank Q9 · default error handling

A JSON error response and the Whitelabel error page — both from BasicErrorController

Your answer included "checked exceptions for common problems" and "separate stack trace logging". Neither is a Boot feature — and Spring's whole exception philosophy is unchecked.

One controller, two representations, chosen by content negotiation:

Client sendsGets
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.

Memory hook: BasicErrorController serves both: Whitelabel HTML for browsers, JSON for everything else. Messages are hidden by default.

⑥ External configuration 6 misses

Papers #1, #7 + bank · where config comes from and what wins three times

Four locations, external beating packaged — and never XML

#7: you ticked "XML files" as a config source. Bank: you rejected valid classpath/external locations. #1: you had the precedence order wrong.

The four default locations, lowest precedence first — the rule is external beats packaged, and /config beats the directory above it:

OrderLocationInside or outside the jar
1 (lowest)classpath:/packaged
2classpath:/config/packaged
3file:./ — the working directoryexternal
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:

PrioritySource
highestcommand-line arguments (--server.port=8081)
SPRING_APPLICATION_JSON
OS environment variables
Java system properties (-D)
application-{profile} files
lowestapplication.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.

Memory hook: classpath root → classpath /config → ./ → ./config, each beating the last. Command line beats everything. properties/YAML only.
Paper #3 + bank · @ConfigurationProperties and @PropertySource

Both go at class level; @PropertySource takes a file, @ConfigurationProperties takes a prefix

Bank: your answers had them on a field, or with the attributes swapped. #3: you thought @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
Takesa prefix stringa resource location
Doesbinds Environment properties onto fieldsadds a file to the Environment
Placementclass or @Bean methodclass
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.

Memory hook: @ConfigurationProperties = prefix, binds a whole object, relaxed + validated. @PropertySource = a .properties file, never YAML.

⑦ Logging 5 misses

Bank ×2 · which file configures which backend asked twice

Each backend has its own file — and a Spring-aware -spring variant

You've mixed logging.properties, logback-spring.xml and log4j2-spring.xml in both directions. The mapping is one-to-one.
BackendNative fileSpring-aware fileStarter
Logback (default)logback.xmllogback-spring.xmlspring-boot-starter-logging
Log4j2log4j2.xmllog4j2-spring.xmlspring-boot-starter-log4j2
JULlogging.propertiesnonefallback 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.

Memory hook: Logback → logback-spring.xml · Log4j2 → log4j2-spring.xml · JUL → logging.properties (no Spring variant).
Papers #6, #9 · levels, the surface, and the internal API

Seven levels · six configurable aspects · Commons Logging internally

#6: you ticked INFO and WARN as invalid levels, and "Logging Anonymization" as a configurable aspect. #9: you answered SLF4J for Spring's internal API.
LevelsConfigurable aspects
OFFLevel — logging.level.<logger>
FATAL (Log4j2 only)Destination — logging.file.name / .path
ERRORPattern — logging.pattern.console / .file
WARNRotation — logging.logback.rollingpolicy.*
INFO (root default)Colour — spring.output.ansi.enabled
DEBUGGroups — logging.group.<name>
TRACENo 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.

Memory hook: OFF · FATAL(Log4j2) · ERROR · WARN · INFO · DEBUG · TRACE. Spring logs via Commons Logging; Logback prints. No anonymisation property exists.

spring.factories & component scanning 6 misses

Papers #7, #9 + bank · what goes in spring.factories three times

META-INF/spring.factories — anything that must exist before the ApplicationContext does

#7: you ticked WEB-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.

KeyRegistersRuns
EnableAutoConfigurationauto-configuration classesduring context creation
AutoConfigurationImportFilterfilters trimming candidates earlybefore conditions
ApplicationContextInitializercontext customisationbefore refresh
ApplicationListenerstartup event listenersbefore refresh
EnvironmentPostProcessorEnvironment mutationbefore the context exists
FailureAnalyzerfriendly startup-failure messageson failure
SpringApplicationRunListenerrun-lifecycle hooksthroughout
# 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.

Memory hook: Root of META-INF. Interface FQN = implementation FQNs, merged across JARs. Never regular beans. Auto-config moved out in 2.7.
Papers #3, #6 + bank · where Boot scans, and what @SpringBootApplication does

The base package is wherever you put the main class — there is no magic package name

#6: you answered "defines a bean" and "defines security" for @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-annotationEffect
@SpringBootConfigurationa @Configurationand the marker Boot's test bootstrapper searches for
@EnableAutoConfigurationloads auto-configuration candidates
@ComponentScanscans 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.

Memory hook: Scan root = the main class's own package, downward. Never a hard-coded name. Never the default package.

The whole section on one page

The facts, one line each — your pre-exam pass
How to use this page (1) Master tables 1 and 2 first — they are worth more than everything else combined. The enabled/exposed distinction alone has cost you marks in four consecutive papers, and on your latest paper it was exactly the two marks between you and a pass. Cover the right-hand columns and reconstruct them. (2) Then table 3 — health. Two codes only, worst-wins aggregation. Eight misses, ten minutes. (3) Do the drills, not the prose. There are 45 questions on this page. Reading builds recognition; only answering builds recall. (4) Use the fabricated-name check. Boot questions are dense with invented names — windows-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.
I'm your teacher — ask me anything. Say "drill the Actuator tables" for a fast round on enabled-vs-exposed and the endpoint roster, "drill auto-configuration" for the conditional mechanism, or "quiz me on Spring Boot" for all of these interleaved in exam wording.
← Dashboard Data Management clinic Spring MVC clinic Lesson 16 · Actuator