Skip to content
QSWEQB
mediumConceptScenarioMidSeniorStaff

How does Spring Boot auto-configuration decide what to create, and how do you override it?

Boot loads a list of auto-configuration classes declared in each jar's AutoConfiguration.imports file, then applies each one only if its conditions hold. Because auto-configurations are evaluated after your own beans are registered, defining a bean yourself makes @ConditionalOnMissingBean back off.

4 min readUpdated 2026-07-26Asked at Amazon, SAP

What the interviewer is scoring

  • Does the candidate explain conditional evaluation rather than describing auto-configuration as magic
  • Whether they know how to get Boot to report its own decisions instead of guessing
  • That relaxed binding and precedence are described with the direction of precedence stated explicitly
  • Whether production-readiness is broken into separate concerns rather than answered with "add Actuator"
  • Can they explain why graceful shutdown alone does not prevent dropped requests

Answer

Discovery

@SpringBootApplication includes @EnableAutoConfiguration, which registers an import selector. That selector reads every META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports file on the classpath — a plain list of class names, one per line. Each entry is a @AutoConfiguration class full of @Bean methods.

This file replaced the older spring.factories key, which was deprecated in Boot 2.7 and removed as a source of auto-configurations in 3.0. Mentioning that is worth a mark, because it is the difference between having written a starter recently and having read a tutorial written in 2019.

Conditions do the work

Loading the list is not the interesting part; the conditions are. Every auto-configuration is guarded, and the guards are what let a hundred candidate configurations coexist while only a handful produce beans:

  • @ConditionalOnClass — the type is on the classpath, so adding a driver jar is what enables the configuration that needs it.
  • @ConditionalOnMissingBean — nothing of this type is already defined. This is the back-off that makes overriding work.
  • @ConditionalOnProperty — a property is set, or set to a particular value.
  • @ConditionalOnWebApplication — this is a servlet or reactive web context.
  • @ConditionalOnMissingClass, @ConditionalOnResource, @ConditionalOnBean and a handful of others for the remaining cases.

The ordering rule is the part candidates most often get backwards. Auto-configurations are processed after all of your own configuration classes have been registered. That is precisely why @ConditionalOnMissingBean is a usable override mechanism: by the time Boot asks whether a DataSource exists, yours already does, so Boot's own definition never gets created. Among themselves, auto-configurations order with @AutoConfigureBefore, @AutoConfigureAfter and @AutoConfigureOrder, which matters when writing a starter and almost never otherwise.

Do not reason about any of this from memory when debugging. Start the application with --debug, and Boot prints the condition evaluation report: every configuration that matched, every one that did not, and the specific condition that decided it. It also exposes the same report through the Actuator conditions endpoint. An answer that reaches for that report rather than for speculation is the answer of someone who has debugged a starter conflict.

Precedence, and the direction it runs

Boot layers property sources into an Environment, and later sources win. Working from highest precedence downwards, the ones you meet in practice are: command-line arguments; SPRING_APPLICATION_JSON; JVM system properties; OS environment variables; profile-specific application-{profile} files outside the jar, then inside it; then plain application files outside the jar, then inside it; then @PropertySource; and last, defaults set programmatically on SpringApplication. In tests, @TestPropertySource and the properties attribute of @SpringBootTest sit above all of it.

Two behaviours around this cause most of the confusion. Relaxed binding means billing.retry-backoff, billing.retryBackoff and BILLING_RETRYBACKOFF all bind to the same property, which is what makes environment variables a first-class configuration channel in a container. And a profile-specific file does not replace the base file — both are loaded, with the profile-specific one taking precedence key by key, so a key absent from the profile file still comes from the base.

Bind groups of properties as a type rather than scattering @Value:

import java.time.Duration;
import org.springframework.boot.context.properties.ConfigurationProperties;

// Constructor binding: a record works directly, and the type is validated and
// converted once at startup instead of per injection point. Needs
// @EnableConfigurationProperties or @ConfigurationPropertiesScan to be picked up.
@ConfigurationProperties(prefix = "billing")
public record BillingProperties(Duration retryBackoff, int maxRetries) { }

Duration conversion means billing.retry-backoff=500ms binds without you parsing anything, and a typo in a bound property fails at startup rather than producing a silently wrong value the way a defaulted @Value does.

Production-readiness is three separate promises

Treating "production-ready" as a synonym for "has Actuator on the classpath" is the commonest weak answer. It is at least three distinct concerns.

Observability. Actuator exposes health, metrics via Micrometer, environment, thread dump and heap dump endpoints. Only health is exposed over HTTP by default; anything else needs management.endpoints.web.exposure.include, and everything you expose needs authorisation, because env and heapdump will hand out your secrets. Running the management endpoints on a separate port, reachable only from inside the cluster, is the usual arrangement.

Correct health semantics. A single health endpoint is not enough for an orchestrator, because liveness and readiness answer different questions. Liveness asks whether the process is broken beyond recovery and should be killed; it must not check downstream dependencies, or a database blip will get every replica restarted at once. Readiness asks whether this instance should receive traffic right now, and that one legitimately checks dependencies. Boot models this with health groups, and it registers the probe groups automatically when it detects it is running in Kubernetes:

management:
  endpoint:
    health:
      probes:
        enabled: true
      group:
        liveness:
          include: livenessState
        readiness:
          include: readinessState,db

Shutting down without dropping work. server.shutdown=graceful makes the web server stop accepting new connections and wait for in-flight requests to finish, bounded by spring.lifecycle.timeout-per-shutdown-phase. That is necessary and not sufficient, and the gap is where good candidates distinguish themselves: the load balancer or kube-proxy does not learn about your shutdown until its next readiness check, so between receiving SIGTERM and being removed from the pool your instance is still being sent requests it has decided not to accept. The fix is sequencing rather than configuration — fail readiness first, wait longer than the readiness probe period, and only then begin the graceful shutdown. Boot supports the first step directly through AvailabilityChangeEvent with a ReadinessState, and Kubernetes supports the wait with a preStop hook.

The rest of the list is unglamorous and still expected: externalised secrets rather than committed ones, structured logging with correlation identifiers, sensible connection-pool and HTTP client timeouts, and a build that produces the same artefact for every environment so that the only thing differing between staging and production is configuration.

Likely follow-ups

  • What is in the condition evaluation report, and how do you get it out of a running app?
  • Why did spring.factories stop being the mechanism for declaring auto-configurations?
  • How do liveness and readiness health groups differ, and what should each one check?
  • How would you write an auto-configuration for an internal starter, and what would you put in @AutoConfigureAfter?

Related questions

Further reading

spring-bootauto-configurationconfiguration-propertiesactuatorproduction-readiness