Spring Boot is not a new framework. It is Spring plus three ideas — starters, auto-configuration, and an embedded server — and the questions are almost always about how those three work rather than about what they do for you.
What it is
What is Spring Boot?
An opinionated layer over the Spring Framework that configures an application from what it finds on the classpath, so a working application needs almost no configuration of its own. It adds no new programming model: the beans, the injection and the annotations are all plain Spring.
What does it give you?
- Starters — one dependency instead of twelve, with versions that agree.
- Auto-configuration — sensible beans created for what you have, and skipped for what you have defined yourself.
- An embedded server — the application is a runnable jar, not a war to deploy.
- Externalised configuration — one ordered mechanism for properties, profiles and overrides.
- Actuator — health, metrics and info endpoints for free.
Why is it called "opinionated"?
Because it makes the choice for you rather than offering the options. Add
spring-boot-starter-webmvc and you get Tomcat, Jackson and Spring MVC configured a
particular way. Every one of those opinions can be overridden — but you only pay for the ones you
want to change.
Starters
What is a starter POM and why is it useful?
A dependency with no code in it, whose job is to pull in a coherent set of others.
spring-boot-starter-data-jpa brings Hibernate, Spring Data JPA, the JDBC starter, a
connection pool and the transaction support — at versions the Spring Boot BOM has already tested
together. That last part matters more than the convenience: version conflicts between Spring, Jackson
and Hibernate used to be a whole afternoon.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.0</version>
</parent>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<!-- Boot 4's name. spring-boot-starter-web still resolves, but is deprecated
in favour of this one and pulls in exactly the same things. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- No <version> on any of these: the parent's BOM supplies it. -->
</dependencies>The naming convention is worth knowing: official starters are
spring-boot-starter-*; third-party ones should be *-spring-boot-starter,
so the prefix stays reserved.
Auto-configuration
What does @EnableAutoConfiguration do?
It tells Boot to look for auto-configuration classes contributed by every jar on the classpath —
listed in META-INF/spring/…AutoConfiguration.imports — and apply the ones whose
conditions are satisfied.
What affects what Spring Boot sets up?
- What is on the classpath. An H2 jar and no datasource URL gets you an in-memory database.
- What properties are set. No
spring.mail.hostmeans noJavaMailSenderis built at all. - What beans you have already defined. Yours always wins.
- Which profiles are active.
How does it decide? With conditional annotations. These are the ones to be able to name:
| Condition | Applies when |
|---|---|
@ConditionalOnClass | a class is on the classpath |
@ConditionalOnMissingClass | it is not |
@ConditionalOnBean | a bean of that type is already defined |
@ConditionalOnMissingBean | it is not — this is the back-off rule |
@ConditionalOnProperty | a property has a given value |
@ConditionalOnWebApplication | it is a web application |
@ConditionalOnMissingBean is the mechanism behind "define your own and Boot backs
off". Auto-configuration is also ordered after your own configuration classes, which is what
makes that reliable.
When something is configured and you cannot see why, ask Boot:
# Prints every auto-configuration, grouped into Positive and Negative matches
# with the exact condition that decided each one.
./mvnw spring-boot:run -Dspring-boot.run.arguments=--debug@SpringBootApplication
What does it expand to? Three annotations:
@SpringBootConfiguration— a@Configurationclass, marked as the application's primary one.@EnableAutoConfiguration@ComponentScan
@SpringBootApplication
@ConfigurationPropertiesScan // finds every @ConfigurationProperties class below this package
public class PizzaSpringbootBackendApplication {
public static void main(String[] args) {
SpringApplication.run(PizzaSpringbootBackendApplication.class, args);
}
}Does Boot do component scanning, and where does it look?
Yes — starting at the package of the @SpringBootApplication class, and downwards.
Nothing above or beside that package is scanned. This is why the class belongs at the root of your
package tree, and why "my @Service is not being found" is nearly always a package
problem.
Properties
Where is Boot's default property source, and how are properties defined?
application.properties or application.yml, on the classpath or in a
config/ directory beside the jar. Profile-specific files
(application-local.properties) are layered on top when that profile is active.
What is the precedence order? Later wins. The ones worth remembering:
- Command-line arguments —
--server.port=9000 SPRING_APPLICATION_JSON- OS environment variables —
SERVER_PORT=9000 - Java system properties —
-Dserver.port=9000 application-{profile}.propertiesoutside the jar, then insideapplication.propertiesoutside the jar, then inside@PropertySource- Defaults set with
SpringApplication.setDefaultProperties
The practical consequence: an environment variable always beats the file baked into the jar, which is exactly what you want in a container.
Relaxed binding. pizza.jwt.expiration-minutes,
pizza.jwt.expirationMinutes and PIZZA_JWT_EXPIRATIONMINUTES all bind to the
same property. That is what makes environment-variable overrides work at all, since a shell variable
cannot contain a dot.
spring.application.name=pizza-springboot-backend
spring.profiles.active=local
server.port=8085
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.open-in-view=false
# ${VAR:default} reads an environment variable and falls back to empty.
# Secrets are NEVER committed - real keys go in a gitignored profile file or the environment.
pizza.stripe.secret-key=${STRIPE_SECRET_KEY:}
pizza.jwt.expiration-minutes=120Bind them with @ConfigurationProperties rather than @Value — see
Core Spring for why.
Embedded servers
Embedded container versus a WAR?
| Embedded | WAR | |
|---|---|---|
| Artifact | an executable jar containing the server | a war deployed into a server |
| Run with | java -jar app.jar | drop it in and restart the server |
| Server version | your dependency, so it is under version control | whatever the operations team installed |
| Fits | containers, microservices, CI | an existing application-server estate |
Which embedded servers are supported? Tomcat (the default), Jetty and Undertow for servlet stacks; Netty for reactive. Swapping is an exclusion plus a starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>To build a deployable war instead: package as war, mark the servlet container
dependency provided, and extend
SpringBootServletInitializer.
Annotations worth recognising
| Annotation | Does |
|---|---|
@SpringBootApplication | configuration + auto-configuration + scan |
@ConfigurationProperties | binds a property namespace to a typed object |
@ConfigurationPropertiesScan | finds those classes without listing them |
@Profile | bean exists only under a named profile |
@ConditionalOnMissingBean | back off if the user defined their own |
@SpringBootTest | load the full context in a test |
@EnableCaching, @EnableAsync, @EnableScheduling | switch on a feature |
A CommandLineRunner or ApplicationRunner bean runs once the context is
ready — the place for startup work, ordered with @Order.
⚠️ Boot 3 → Boot 4
These are the changes that turn a correct answer into a wrong one.
spring-boot-starter-web→spring-boot-starter-webmvc. The old name is deprecated but still present in the BOM and pulls in exactly the same dependencies, so an unchanged pom keeps building.spring-boot-starter-aop→spring-boot-starter-aspectj. This one is not the same story: the old name is gone from the BOM entirely, so an unchanged pom fails with "version is missing" — an error that never mentions the rename. Two starters renamed in the same release, with two different migration stories, which is exactly why you have to check each one instead of assuming.- Auto-configuration was split into per-technology modules, so packages moved —
AutoConfigureMockMvcis now inorg.springframework.boot.webmvc.test.autoconfigure, andDefaultJmsListenerContainerFactoryConfigurerinorg.springframework.boot.jms.autoconfigure. - Retry moved into the core container.
@Retryablenow lives inorg.springframework.resilience.annotationand is enabled with@EnableResilientMethods. Thespring-retrydependency and@EnableRetryare no longer needed. Note it has no@Recoverequivalent — it rethrows once the retries are exhausted. - The modularisation is not uniform.
spring-boot-starter-cachekept its name and is still BOM-managed, while the aop starter lost its.
And carried over from Boot 3: Java 17 minimum, and jakarta.* everywhere.
What to remember
- Boot adds no programming model — it is configuration, packaging and defaults.
- Auto-configuration is conditional, and
@ConditionalOnMissingBeanis why your own bean always wins. - Component scanning starts at the
@SpringBootApplicationpackage and goes down only. - Property precedence: command line > environment > system properties > profile file >
application.properties. --debugprints exactly which conditions matched and which did not.