Spring Study Guide – Spring Boot

August 10, 20266 min readUpdated 8/18/2026

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?

  1. What is on the classpath. An H2 jar and no datasource URL gets you an in-memory database.
  2. What properties are set. No spring.mail.host means no JavaMailSender is built at all.
  3. What beans you have already defined. Yours always wins.
  4. Which profiles are active.

How does it decide? With conditional annotations. These are the ones to be able to name:

ConditionApplies when
@ConditionalOnClassa class is on the classpath
@ConditionalOnMissingClassit is not
@ConditionalOnBeana bean of that type is already defined
@ConditionalOnMissingBeanit is not — this is the back-off rule
@ConditionalOnPropertya property has a given value
@ConditionalOnWebApplicationit 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 @Configuration class, 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:

  1. Command-line arguments — --server.port=9000
  2. SPRING_APPLICATION_JSON
  3. OS environment variables — SERVER_PORT=9000
  4. Java system properties — -Dserver.port=9000
  5. application-{profile}.properties outside the jar, then inside
  6. application.properties outside the jar, then inside
  7. @PropertySource
  8. 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=120

Bind them with @ConfigurationProperties rather than @Value — see Core Spring for why.

Embedded servers

Embedded container versus a WAR?

EmbeddedWAR
Artifactan executable jar containing the servera war deployed into a server
Run withjava -jar app.jardrop it in and restart the server
Server versionyour dependency, so it is under version controlwhatever the operations team installed
Fitscontainers, microservices, CIan 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

AnnotationDoes
@SpringBootApplicationconfiguration + auto-configuration + scan
@ConfigurationPropertiesbinds a property namespace to a typed object
@ConfigurationPropertiesScanfinds those classes without listing them
@Profilebean exists only under a named profile
@ConditionalOnMissingBeanback off if the user defined their own
@SpringBootTestload the full context in a test
@EnableCaching, @EnableAsync, @EnableSchedulingswitch 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-webspring-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-aopspring-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 — AutoConfigureMockMvc is now in org.springframework.boot.webmvc.test.autoconfigure, and DefaultJmsListenerContainerFactoryConfigurer in org.springframework.boot.jms.autoconfigure.
  • Retry moved into the core container. @Retryable now lives in org.springframework.resilience.annotation and is enabled with @EnableResilientMethods. The spring-retry dependency and @EnableRetry are no longer needed. Note it has no @Recover equivalent — it rethrows once the retries are exhausted.
  • The modularisation is not uniform. spring-boot-starter-cache kept 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 @ConditionalOnMissingBean is why your own bean always wins.
  • Component scanning starts at the @SpringBootApplication package and goes down only.
  • Property precedence: command line > environment > system properties > profile file > application.properties.
  • --debug prints exactly which conditions matched and which did not.