Spring Boot Setup: Starters, Auto-Configuration and Your First App
Backend DevelopmentLesson 1 of 45 min read
What you'll learn
- Generate a Spring Boot 3 project and read its pom.xml, parent and starters
- Explain what @SpringBootApplication bundles and why package location matters
- Describe auto-configuration as conditional beans and override one safely
- Diagnose the five startup errors beginners hit most often
On this page
You add a single dependency to a new project, run main, and a web server is listening on port 8080, JSON is converted automatically and a metrics registry exists. Nobody wrote a line of configuration. That feels like magic until the first time something starts differently on your colleague's machine, and then it feels like a haunted house. This lesson takes the magic apart so that when startup misbehaves you know which door to open.
The three pieces behind the magic
Spring Boot is not a different framework from Spring. It is a set of conventions on top of the Spring Framework, delivered through three mechanisms that are easy to confuse.
- Starters are dependency bundles.
spring-boot-starter-webis just a pom that pulls in Spring MVC, an embedded Tomcat, Jackson and logging at versions known to work together. It contains almost no code of its own. - The parent (or BOM) pins those versions. Because
spring-boot-starter-parentmanages them, you writespring-boot-starter-webwithout a<version>tag. - Auto-configuration is the code that reacts to what is on the classpath. If a web starter is present, a configuration class creates the servlet container and MVC beans. If H2 and JPA are present, one creates an in-memory
DataSource. Each of those classes is guarded by conditions.
Spring Boot 3 requires Java 17 or newer and moved from the javax.* namespaces to jakarta.* (for example jakarta.persistence.Entity). Everything in this course assumes Boot 3.x. Here is a minimal build file; use whichever Boot version start.spring.io offers you instead of the one below.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.0</version> <!-- use the version Initializr generates for you -->
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>lockerhub</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>A parcel-locker kiosk in three small classes
Our running example is the backend of a parcel-locker kiosk: a courier drops a parcel in a locker, the recipient has three days to collect it, and the kiosk asks the service whether a locker's hold has expired. Notice how little ceremony the application class needs.
package com.example.lockerhub;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LockerhubApplication {
public static void main(String[] args) {
SpringApplication.run(LockerhubApplication.class, args);
}
}package com.example.lockerhub;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import org.springframework.stereotype.Service;
@Service
public class PickupPolicy {
private static final Duration HOLD_TIME = Duration.ofDays(3);
private final Clock clock;
// One constructor: Spring injects it without any @Autowired.
public PickupPolicy(Clock clock) {
this.clock = clock;
}
public boolean isExpired(Instant droppedAt) {
return clock.instant().isAfter(droppedAt.plus(HOLD_TIME));
}
}package com.example.lockerhub;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LockerController {
record PickupStatus(String lockerId, boolean expired) {}
private final PickupPolicy policy;
// Stand-in for a database: locker L-12 received a parcel five hours ago.
private final Map<String, Instant> droppedAt = new ConcurrentHashMap<>(
Map.of("L-12", Instant.now().minus(Duration.ofHours(5))));
public LockerController(PickupPolicy policy) {
this.policy = policy;
}
@GetMapping("/api/lockers/{lockerId}/pickup-status")
public PickupStatus status(@PathVariable String lockerId) {
Instant dropped = droppedAt.get(lockerId);
return new PickupStatus(lockerId, dropped != null && policy.isExpired(dropped));
}
}Run the application and call curl http://localhost:8080/api/lockers/L-12/pickup-status. Five hours into a three-day hold, the body is {"lockerId":"L-12","expired":false}. But wait: nothing in our code created a Clock bean, so what did Spring inject into PickupPolicy? That is the next section.
What @SpringBootApplication actually bundles
@SpringBootApplication is shorthand for three annotations: @SpringBootConfiguration (this class is a configuration class), @EnableAutoConfiguration (switch on the conditional configuration described above) and @ComponentScan (find @Component, @Service, @RestController and friends). The scan starts at the package of the annotated class and walks downward. Put your application class in com.example.lockerhub and a service in com.example.billing, and that service is invisible to Spring.
Auto-configuration is just conditional beans
Our missing Clock was not provided by Boot, so we must supply it. Here is how a library author (or you) writes a default that application code can override. @ConditionalOnMissingBean means: create this bean only if nobody else defined one of this type.
package com.example.lockerhub;
import java.time.Clock;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ClockConfig {
@Bean
@ConditionalOnMissingBean
public Clock clock() {
return Clock.systemUTC();
}
}In a test you can define your own Clock bean (a fixed one) and this default quietly steps aside. That is the whole design of auto-configuration: sensible defaults that back off the moment you define your own. Injecting a Clock rather than calling Instant.now() inside the service is what makes time-based rules testable. Here is that rule as plain Java, so you can see it behave at the boundary of the three-day window.
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
public class PickupDemo {
record PickupPolicy(Clock clock, Duration holdTime) {
boolean isExpired(Instant droppedAt) {
return clock.instant().isAfter(droppedAt.plus(holdTime));
}
}
public static void main(String[] args) {
Instant droppedAt = Instant.parse("2030-05-01T10:00:00Z");
Clock lastMoment = Clock.fixed(Instant.parse("2030-05-04T10:00:00Z"), ZoneOffset.UTC);
Clock oneSecondLate = Clock.fixed(Instant.parse("2030-05-04T10:00:01Z"), ZoneOffset.UTC);
Duration hold = Duration.ofDays(3);
System.out.println(new PickupPolicy(lastMoment, hold).isExpired(droppedAt));
System.out.println(new PickupPolicy(oneSecondLate, hold).isExpired(droppedAt));
}
}Output
false true
Third-party libraries ship their own auto-configuration by listing classes in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. You will rarely write one, but knowing this file exists demystifies how a starter you add can wire itself in.
| Starter | Brings in | Typical use |
|---|---|---|
| spring-boot-starter-web | Spring MVC, embedded Tomcat, Jackson | REST APIs and web apps |
| spring-boot-starter-data-jpa | Spring Data JPA, Hibernate | Relational persistence |
| spring-boot-starter-validation | Jakarta Bean Validation (Hibernate Validator) | @Valid request checks |
| spring-boot-starter-actuator | Health, metrics and info endpoints | Operations and monitoring |
| spring-boot-starter-test | JUnit, Mockito, AssertJ, Spring Test | Unit and slice tests |
Common mistakes and the errors they cause
- Application class in the wrong package. A controller outside the scanned package gives you a 404 and no startup error. Keep every class at or below the package of
@SpringBootApplication. - Missing bean. Forgetting the
Clockbean above printsParameter 0 of constructor in com.example.lockerhub.PickupPolicy required a bean of type 'java.time.Clock' that could not be found.followed by anAction:line telling you to define one. - JPA on the classpath, no database.
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.Add an embedded database such as H2 for development or setspring.datasource.url. - Port already taken.
Web server failed to start. Port 8080 was already in use.Stop the other process or setserver.port. - Wrong Java version or old imports. Running Boot 3 on Java 11 fails with an
UnsupportedClassVersionError(class file version 61.0), andpackage javax.persistence does not existmeans a copied example predates the move tojakarta.*.
How this shows up in real projects and interviews
Interviewers love "what does @SpringBootApplication do?" and "how does auto-configuration decide what to create?". A good answer names the three annotations, mentions conditions such as @ConditionalOnClass and @ConditionalOnMissingBean, and ends with the debugging trick above. On a real team, the same knowledge is what lets you replace the default ObjectMapper or DataSource without fighting the framework. When you are ready to look at roles that expect it, search Spring-related openings.
Try it yourself
Extend the lockerhub project: add a LockerReminderPolicy service that reports whether a parcel is within 12 hours of expiry, injecting the same Clock. Then write a @TestConfiguration that supplies Clock.fixed(...) and prove the default bean steps aside by asserting the fixed time is used.
Show a hint
Reuse the Duration arithmetic from PickupPolicy. In the test, import your @TestConfiguration class with @Import on the test class so Boot sees your Clock bean as well as the default one.
Key takeaways
- Starters are dependency bundles, the parent pins versions, and auto-configuration creates beans conditionally based on the classpath.
@SpringBootApplication=@SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan, and component scanning starts at that class's package.@ConditionalOnMissingBeanlets a default step aside when you define your own bean, which is also how tests substitute fixed clocks or fakes.- Run with
--debugto see the conditions report when a bean is missing or unexpectedly present.
Test yourself
A trick from this lesson's ride: A starter is a shopping basket, not a shop: it carries libraries, not logic. Four more tricks like it wait along the route, each followed by a question.
Frequently asked questions
What is the difference between Spring and Spring Boot?
Spring is the underlying framework with dependency injection, MVC, data access and more. Spring Boot adds starters, auto-configuration and an embedded server so you can run a production-style application with very little setup code. You still use Spring annotations everywhere in a Boot app.
Why does Spring Boot not need an application server like Tomcat installed?
The web starter embeds Tomcat as a library and starts it from main. You build one executable jar and run it with java -jar, instead of deploying a WAR into a separately installed server. You can swap Tomcat for Jetty or Undertow by changing the dependency.
How does @ConditionalOnMissingBean work in Spring Boot?
It tells Spring to register a bean only if the context does not already contain a bean of that type or name. Auto-configuration uses it so your own bean definitions win, while users who define nothing still get a working default.