Spring Boot Cheat Sheet
This Spring Boot cheat sheet covers 28 sections of Spring Boot 4: starters and application.properties, profiles and @ConfigurationProperties, beans and auto-configuration, REST controllers with validation and problem details, API versioning, Spring Data JPA, Spring Security 7, actuator, testing, Docker images and the upgrade from Boot 3. Every output comes from a real Boot 4.1.1 app. Check your properties for Boot 4 and generate build files and Dockerfiles without leaving the page.
NoSuchBeanDefinitionException
Nothing matches that search. Try a shorter keyword such as profile, actuator or jpa. Several words are combined, so every one of them has to match. Keys: / focuses the search, Esc clears it, t switches the theme.
Toolbox & First App
check properties for Boot 4, convert properties and YAML, map keys to environment variables, read cron expressions
Builders: Build File, REST Resource, Database Config, Dockerfile
A First Application
The main class
package com.example.shop;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ShopApplication {
public static void main(String[] args) {
SpringApplication.run(ShopApplication.class, args);
}
}
Build and run it
./mvnw package java -jar target/shop-1.0.0.jar
Project Setup
start.spring.io, Boot 4 starters, the parent POM and the wrappers
Generate a Project
From the command line
curl -s https://start.spring.io/starter.zip -d type=maven-project -d javaVersion=25 -d dependencies=web,actuator,data-jpa,validation -d artifactId=shop -o shop.zip && unzip -Z1 shop.zip | sort
Boot 4 Starters
Modular starters, each with a test companion
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.1</version>
</parent>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Renamed starters
| Boot 3 starter | Boot 4 starter |
|---|---|
| spring-boot-starter-web | spring-boot-starter-webmvc |
| spring-boot-starter-web-services | spring-boot-starter-webservices |
| spring-boot-starter-oauth2-client | spring-boot-starter-security-oauth2-client |
| spring-boot-starter-oauth2-resource-server | spring-boot-starter-security-oauth2-resource-server |
| spring-boot-starter-oauth2-authorization-server | spring-boot-starter-security-oauth2-authorization-server |
| spring-boot-starter and spring-boot-starter-test, all auto-configuration at once | spring-boot-starter-classic and spring-boot-starter-test-classic |
Gradle
build.gradle.kts
plugins {
java
id("org.springframework.boot") version "4.1.1"
id("io.spring.dependency-management") version "1.1.7"
}
java {
toolchain { languageVersion = JavaLanguageVersion.of(25) }
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-webmvc")
implementation("org.springframework.boot:spring-boot-starter-actuator")
testImplementation("org.springframework.boot:spring-boot-starter-webmvc-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
Everyday Commands
Maven and Gradle side by side
| Task | Maven | Gradle |
|---|---|---|
| run the app | ./mvnw spring-boot:run | ./gradlew bootRun |
| run with a profile | ./mvnw spring-boot:run -Dspring-boot.run.profiles=dev | ./gradlew bootRun --args='--spring.profiles.active=dev' |
| build the jar | ./mvnw package | ./gradlew bootJar |
| skip tests | ./mvnw package -Dmaven.test.skip=true | ./gradlew bootJar -x test |
| container image | ./mvnw spring-boot:build-image | ./gradlew bootBuildImage |
| native executable | ./mvnw -Pnative native:compile | ./gradlew nativeCompile |
| dependency tree | ./mvnw dependency:tree | ./gradlew dependencies |
Properties & Configuration
application.properties, YAML, precedence and relaxed binding
Where Configuration Comes From
application.properties and its YAML twin
spring.application.name=shop server.port=8080 spring.datasource.url=jdbc:postgresql://localhost:5432/shop spring.jpa.open-in-view=false shop.currency=EUR shop.countries=RO,DE,FR
Precedence, highest first
| Source | Example |
|---|---|
| command-line arguments | java -jar app.jar --server.port=9090 |
| SPRING_APPLICATION_JSON | SPRING_APPLICATION_JSON='{"server":{"port":9090}}' |
| Java system properties | java -Dserver.port=9090 -jar app.jar |
| OS environment variables | SERVER_PORT=9090 |
| profile-specific files outside the jar | ./config/application-prod.properties |
| application files outside the jar | ./application.properties next to the jar |
| profile-specific files in the jar | src/main/resources/application-prod.properties |
| application files in the jar | src/main/resources/application.properties |
| @PropertySource and defaults | on @Configuration classes, SpringApplication.setDefaultProperties |
Relaxed Binding
One property, four spellings
| Where | Spelling of shop.max-items |
|---|---|
| properties and YAML | shop.max-items, the canonical kebab case |
| environment variable | SHOP_MAXITEMS or SHOP_MAX_ITEMS |
| list element in an environment variable | SHOP_COUNTRIES_0_=RO |
| system property or argument | --shop.max-items=10 |
An environment variable that fails validation
SHOP_MAX_ITEMS=200 java -jar target/shop-1.0.0.jar
Durations and Sizes
How Boot parses 30s and 10MB
import org.springframework.boot.convert.DurationStyle;
import org.springframework.util.unit.DataSize;
System.out.println(DurationStyle.detectAndParse("30s"));
System.out.println(DurationStyle.detectAndParse("PT1H30M"));
System.out.println(DurationStyle.detectAndParse("PT0.5S"));
System.out.println(DurationStyle.detectAndParse("250"));
System.out.println(DataSize.parse("10MB").toBytes());
System.out.println(DataSize.parse("512KB").toBytes());
Encoding
A UTF-8 é in application.properties
import java.nio.file.Files;
import java.nio.charset.StandardCharsets;
import org.springframework.boot.env.PropertiesPropertySourceLoader;
import org.springframework.core.io.FileSystemResource;
var file = Files.createTempFile("app", ".properties");
Files.writeString(file, "shop.note=café\nshop.escaped=caf\\u00e9\n", StandardCharsets.UTF_8);
var source = new PropertiesPropertySourceLoader().load("app", new FileSystemResource(file)).get(0);
for (String key : new String[] {"shop.note", "shop.escaped"}) {
String value = (String) source.getProperty(key);
System.out.println(key + ": " + value.length() + " chars " + value.chars().mapToObj(Integer::toHexString).toList());
}
YAML Surprises
Unquoted values YAML 1.1 rewrites
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.env.EnumerablePropertySource;
var yaml = """
shop:
file-mode: 0755
opens-at: 8:30
gift-wrap: on
country: NO
quoted-mode: "0755"
""";
var source = (EnumerablePropertySource<?>) new YamlPropertySourceLoader().load("demo", new ByteArrayResource(yaml.getBytes())).get(0);
for (String name : source.getPropertyNames()) System.out.println(name + " = " + source.getProperty(name));
Typed Configuration
@ConfigurationProperties records, validation and metadata
@ConfigurationProperties
A record bound to shop.*
@Validated
@ConfigurationProperties("shop")
public record ShopProperties(
@NotBlank String currency,
@Min(1) @Max(100) int maxItems,
@DefaultValue("50") BigDecimal freeShippingOver,
@DefaultValue("30s") Duration paymentTimeout,
List<String> countries) {
}
What the record receives
shop.currency=EUR shop.max-items=10 shop.countries=RO,DE,FR
@Value or @ConfigurationProperties
Pick the right one
| Need | Use |
|---|---|
| a group of related settings | @ConfigurationProperties record |
| validation, IDE auto-completion, metadata | @ConfigurationProperties with spring-boot-configuration-processor |
| one value in one class | @Value("${shop.currency}") |
| a computed value | @Value("#{...}") SpEL, sparingly |
Profiles
profile files, activation, groups and @Profile
Activate a Profile
Three ways to switch
java -jar target/shop-1.0.0.jar --spring.profiles.active=prod SPRING_PROFILES_ACTIVE=prod java -jar target/shop-1.0.0.jar ./mvnw spring-boot:run -Dspring-boot.run.profiles=dev
Beans for one profile
@Component
@Profile("demo")
public class DemoRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
System.out.println("only in the demo profile");
}
}
Groups and Multi-Document Files
One file, several profiles
spring.profiles.group.prod=postgres,json-logs logging.level.root=info #--- spring.config.activate.on-profile=json-logs logging.structured.format.console=ecs #--- spring.config.activate.on-profile=dev logging.level.com.example=debug
Beans & Dependency Injection
components, @Bean methods, qualifiers and conditions
Declaring Beans
Component scanning and constructor injection
@Service
public class OrderService {
private final OrderRepository orders;
private final ShopProperties shop;
public OrderService(OrderRepository orders, ShopProperties shop) {
this.orders = orders;
this.shop = shop;
}
}
@Bean methods for classes you do not own
@Configuration
public class ClockConfig {
@Bean
Clock clock() {
return Clock.systemUTC();
}
}
Choosing Between Beans
Two beans of the same type
| Annotation | Effect |
|---|---|
| @Primary | the default choice when several candidates exist |
| @Qualifier("fast") | pick a specific bean by name or qualifier |
| List<PaymentProvider> | inject all of them, in @Order order |
| @ConditionalOnProperty(name = "shop.payments", havingValue = "stripe") | create the bean only when the property matches |
| @ConditionalOnMissingBean | create a default unless the application defined its own |
Scopes and lifecycle
| Scope | Instances |
|---|---|
| singleton | one per application context, the default |
| prototype | a new one at every injection or getBean call |
| request, session | one per HTTP request or session, in web applications |
Auto-Configuration
how Boot decides what to configure, the conditions report and your own auto-configuration
How It Decides
Ask Boot why
java -jar target/shop-1.0.0.jar --debug
A positive match from the same report
Turn It Off
Exclude an auto-configuration
spring.autoconfigure.exclude=org.springframework.boot.security.autoconfigure.UserDetailsServiceAutoConfiguration
Write Your Own
An auto-configuration class and its registration
@AutoConfiguration
@ConditionalOnClass(AuditClient.class)
@EnableConfigurationProperties(AuditProperties.class)
public class AuditAutoConfiguration {
@Bean
@ConditionalOnMissingBean
AuditClient auditClient(AuditProperties properties) {
return new AuditClient(properties.url());
}
}
REST Controllers
@RestController, mappings, request data and responses
A Controller
Read, list and create orders
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService service;
public OrderController(OrderService service) {
this.service = service;
}
@GetMapping
public PagedModel<Order> list(@RequestParam(required = false) Order.Status status,
@PageableDefault(size = 2, sort = "id") Pageable pageable) {
return new PagedModel<>(service.list(status, pageable));
}
@GetMapping("/{id}")
public Order get(@PathVariable long id) {
return service.find(id);
}
@PostMapping
public ResponseEntity<Order> create(@Valid @RequestBody NewOrder request) {
Order order = service.create(request);
return ResponseEntity.created(URI.create("/api/orders/" + order.getId())).body(order);
}
}
GET one order
curl -s localhost:8080/api/orders/1
POST returns 201 and a Location header
curl -s -i -u admin:change-me -X POST localhost:8080/api/orders -H "Content-Type: application/json" -d '{"sku":"LAMP-01","qty":2}'
Request Data
Where each annotation reads from
| Annotation | Reads | Example |
|---|---|---|
| @PathVariable | a URI template segment | /api/orders/{id} |
| @RequestParam | the query string or a form field | ?status=PAID |
| @RequestBody | the JSON body, via Jackson | {"sku":"LAMP-01"} |
| @RequestHeader | a request header | API-Version: 2 |
| @CookieValue | a cookie | session=... |
| Pageable | page, size and sort parameters | ?page=0&size=20&sort=total,desc |
Validation
Jakarta Bean Validation on request bodies, parameters and configuration
Validate Request Bodies
Constraints on a record
public record NewOrder(
@NotBlank @Pattern(regexp = "[A-Z]+-\\d+") String sku,
@Min(1) @Max(10) int qty) {
}
An invalid request
curl -s -u admin:change-me -X POST localhost:8080/api/orders -H "Content-Type: application/json" -d '{"sku":"lamp","qty":0}'
Common Constraints
jakarta.validation.constraints
| Constraint | Checks |
|---|---|
| @NotNull, @NotEmpty, @NotBlank | not null; not null and not empty; contains a non-space character |
| @Size(min, max) | length of a string or size of a collection |
| @Min, @Max, @Positive, @PositiveOrZero | numeric bounds |
| @DecimalMin, @Digits | decimal bounds and precision, for BigDecimal money |
| @Email, @Pattern | format checks |
| @Past, @Future, @PastOrPresent | dates and times |
| @Valid on a field | cascade into a nested object or list elements |
Error Handling
ProblemDetail, @RestControllerAdvice and RFC 9457 responses
Problem Details
Turn them on for Spring MVC exceptions
spring.mvc.problemdetails.enabled=true
Map your exceptions with @RestControllerAdvice
@RestControllerAdvice
public class ApiErrors extends ResponseEntityExceptionHandler {
@ExceptionHandler(OrderNotFoundException.class)
ProblemDetail notFound(OrderNotFoundException ex) {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
problem.setType(URI.create("https://example.com/problems/order-not-found"));
problem.setTitle("Order not found");
problem.setProperty("orderId", ex.getId());
return problem;
}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatusCode status, WebRequest request) {
ProblemDetail problem = ex.getBody();
Map<String, String> errors = ex.getBindingResult().getFieldErrors().stream()
.collect(Collectors.toMap(e -> e.getField(), e -> e.getDefaultMessage(), (a, b) -> a + "; " + b));
problem.setProperty("errors", errors);
return handleExceptionInternal(ex, problem, headers, status, request);
}
}
The response for a missing order
curl -s -i localhost:8080/api/orders/9
A body that is not valid JSON
curl -s -u admin:change-me -X POST localhost:8080/api/orders -H "Content-Type: application/json" -d '{"sku":"LAMP-01","qty":2'
API Versioning
Spring Framework 7 versioned mappings and Boot 4 properties
Versioned Endpoints
Configure how the version is read
spring.mvc.apiversion.use.header=API-Version spring.mvc.apiversion.default=1
Two versions of one endpoint
@GetMapping(path = "/{id}", version = "1")
public Order get(@PathVariable long id) {
return service.find(id);
}
@GetMapping(path = "/{id}", version = "2")
public OrderView getV2(@PathVariable long id) {
return OrderView.of(service.find(id));
}
Ask for version 2
curl -s -H "API-Version: 2" localhost:8080/api/orders/1
An unsupported version
curl -s -H "API-Version: 3" localhost:8080/api/orders/1
HTTP Clients
RestClient, HTTP service interfaces and timeouts
RestClient
A fluent, synchronous client
import org.springframework.web.client.RestClient;
import org.springframework.web.client.HttpClientErrorException;
import java.util.Map;
var client = RestClient.builder().baseUrl("https://httpbingo.org").build();
Map<?, ?> body = client.get().uri("/get?shop={name}", "demo").retrieve().body(Map.class);
System.out.println(body.get("args"));
try {
client.get().uri("/status/404").retrieve().toBodilessEntity();
} catch (HttpClientErrorException.NotFound e) {
System.out.println(e.getStatusCode() + " " + e.getClass().getSimpleName());
}
HTTP Service Interfaces
Declare the API as an interface
@HttpExchange("/anything")
public interface EchoClient {
@GetExchange
Map<String, Object> echo(@RequestParam String q);
}
@Configuration
@ImportHttpServices(group = "echo", types = EchoClient.class)
public class ClientsConfig {
}
Point the group at a server and call it
spring.http.serviceclient.echo.base-url=https://httpbingo.org spring.http.serviceclient.echo.read-timeout=5s
Which Client
RestClient, WebClient, RestTemplate
| Client | Use for |
|---|---|
| RestClient | blocking code in Spring MVC apps; works well with virtual threads |
| HTTP service interface | typed clients for APIs you call often |
| WebClient | reactive code with WebFlux, or streaming |
| RestTemplate | existing code only; Spring Framework 7.1 milestones mark it @Deprecated(forRemoval = true) |
Data JPA
entities, repositories, derived queries and paging
Entity and Repository
An entity
@Entity
@Table(name = "orders")
public class Order {
public enum Status { PENDING, PAID, SHIPPED }
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String sku;
private int qty;
@Enumerated(EnumType.STRING)
private Status status = Status.PENDING;
private BigDecimal total;
private Instant createdAt = Instant.now();
protected Order() {
}
}
A repository with derived and custom queries
public interface OrderRepository extends JpaRepository<Order, Long> {
Page<Order> findByStatus(Order.Status status, Pageable pageable);
long countByStatus(Order.Status status);
@Query("select o from Order o where o.total >= :min order by o.total desc")
List<Order> findLarge(BigDecimal min);
}
Paging and sorting from the URL
curl -s "localhost:8080/api/orders?status=PAID&page=0&size=5&sort=total,desc"
Derived Query Keywords
Method names Spring Data understands
| Method name | Generated condition |
|---|---|
| findBySkuAndStatus | where sku = ?1 and status = ?2 |
| findByTotalGreaterThanEqual | where total >= ?1 |
| findByCreatedAtBetween | where created_at between ?1 and ?2 |
| findBySkuIn(Collection) | where sku in (?1) |
| findBySkuContainingIgnoreCase | where upper(sku) like upper('%' + ?1 + '%') |
| findFirst10ByStatusOrderByCreatedAtDesc | top 10 rows, newest first |
| existsBySku, countByStatus, deleteByStatus | exists, count and delete queries |
Settings That Matter
application.properties for JPA
spring.jpa.open-in-view=false spring.jpa.hibernate.ddl-auto=validate spring.jpa.show-sql=false logging.level.org.hibernate.SQL=debug
Transactions
@Transactional rules and the proxy traps
@Transactional
On service methods
@Transactional(readOnly = true)
public Order find(long id) {
return orders.findById(id).orElseThrow(() -> new OrderNotFoundException(id));
}
@Transactional
public Order create(NewOrder request) {
return orders.save(new Order(request.sku(), request.qty(), priceFor(request)));
}
Why It Did Not Roll Back
The classic traps
| Situation | What happens |
|---|---|
| calling a @Transactional method from the same class | no transaction: the call skips the proxy |
| @Transactional on a private method | ignored |
| a checked exception is thrown | commit, unless rollbackFor = Exception.class |
| the exception is caught inside the method | commit |
| LazyInitializationException after the method returned | the session is closed; fetch what you need with a join fetch or an entity graph |
Database Migrations
Flyway and Liquibase with Boot
Flyway
Add the starter and a first script
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-flyway</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
src/main/resources/db/migration/V1__create_orders.sql
create table orders (
id bigint generated by default as identity primary key,
sku varchar(40) not null,
qty int not null,
status varchar(16) not null,
total numeric(10, 2) not null,
created_at timestamp with time zone not null
);
Rules for Migrations
What keeps deployments safe
| Rule | Why |
|---|---|
| set spring.jpa.hibernate.ddl-auto=validate | Hibernate checks the schema but never changes it |
| one change per script, forward only | rollbacks are new scripts, not edits |
| expand, then contract | add the new column, deploy, migrate data, then drop the old one |
| same scripts in tests | Testcontainers runs them against the real database engine |
Security
SecurityFilterChain, users, HTTP Basic and JWT resource servers
A SecurityFilterChain
Public reads, admin writes
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain api(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health/**", "/actuator/info").permitAll()
.requestMatchers(HttpMethod.GET, "/api/**").permitAll()
.requestMatchers("/error").permitAll()
.anyRequest().hasRole("ADMIN"))
.httpBasic(Customizer.withDefaults())
.csrf(csrf -> csrf.disable())
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.build();
}
}
An anonymous write
curl -s -i -X POST localhost:8080/api/orders -H "Content-Type: application/json" -d '{"sku":"LAMP-01","qty":2}'
Users and Tokens
Development user from properties
spring.security.user.name=admin spring.security.user.password=change-me spring.security.user.roles=ADMIN
A JWT resource server
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://login.example.com/realms/shop
Passwords
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
Actuator
health, info, metrics and exposing endpoints safely
Exposing Endpoints
Choose what is reachable over HTTP
management.endpoints.web.exposure.include=health,info,metrics management.endpoint.health.show-details=when-authorized management.info.env.enabled=true management.info.java.enabled=true management.info.os.enabled=true info.app.version=@project.version@
Health for anonymous callers
curl -s localhost:8080/actuator/health
One component, with credentials
curl -s -u admin:change-me localhost:8080/actuator/health/db
Build and runtime info
curl -s localhost:8080/actuator/info
Metrics
Request timings for one URI
curl -s -u admin:change-me "localhost:8080/actuator/metrics/http.server.requests?tag=uri:/api/orders/%7Bid%7D"
Endpoints worth knowing
| Endpoint | Shows |
|---|---|
| health, info | status and build information |
| metrics, prometheus | Micrometer meters |
| loggers | log levels, changeable at runtime with POST |
| env, configprops | resolved properties, with values masked by default |
| beans, conditions, mappings | application context, auto-configuration report, request mappings |
| threaddump, heapdump | diagnostics; heapdump can contain secrets |
| sbom | the software bill of materials embedded at build time |
| shutdown | stops the app; access is none by default |
Logging
levels, files, groups and structured JSON logs
Levels and Files
Common settings
logging.level.root=info logging.level.com.example.shop=debug logging.level.org.hibernate.SQL=debug logging.file.name=logs/shop.log logging.group.persistence=org.hibernate,com.zaxxer.hikari logging.level.persistence=warn
Structured Logging
JSON logs in Elastic Common Schema
java -jar target/shop-1.0.0.jar --logging.structured.format.console=ecs
Observability
Micrometer, tracing and the OpenTelemetry starter
OpenTelemetry
Export traces and metrics over OTLP
management.tracing.sampling.probability=0.1 management.opentelemetry.tracing.export.otlp.endpoint=http://otel-collector:4318/v1/traces
What gets instrumented
| Signal | Source |
|---|---|
| HTTP server and client timings | Spring MVC, RestClient and WebClient observations |
| database pool and queries | HikariCP metrics, and JDBC observations with an extra library |
| trace and span ids in logs | added to the log pattern and to structured logs automatically when tracing is on |
| your own code | @Observed on methods, or the ObservationRegistry API |
Testing
test slices, MockMvcTester, @MockitoBean, RestTestClient and Testcontainers
Test Slices
Load only what a test needs
| Annotation | Loads | Starter |
|---|---|---|
| @WebMvcTest(OrderController.class) | MVC infrastructure and one controller | spring-boot-starter-webmvc-test |
| @DataJpaTest | JPA, repositories, an embedded database, rollback per test | spring-boot-starter-data-jpa-test |
| @JsonTest | Jackson configuration | spring-boot-starter-jackson-test |
| @RestClientTest | RestClient with a mock server | spring-boot-starter-restclient-test |
| @SpringBootTest | the whole application context | spring-boot-starter-test |
A controller test with MockMvcTester and @MockitoBean
@WebMvcTest(OrderController.class)
@Import(SecurityConfig.class)
class OrderControllerTest {
@Autowired
MockMvcTester mvc;
@MockitoBean
OrderService service;
@Test
void returnsOrder() {
given(service.find(1)).willReturn(new Order("LAMP-01", 1, new BigDecimal("39.50")));
assertThat(mvc.get().uri("/api/orders/1"))
.hasStatusOk()
.bodyJson().extractingPath("$.sku").isEqualTo("LAMP-01");
}
@Test
void missingOrderIsProblemJson() {
given(service.find(9)).willThrow(new OrderNotFoundException(9));
assertThat(mvc.get().uri("/api/orders/9"))
.hasStatus(HttpStatus.NOT_FOUND)
.bodyJson().extractingPath("$.title").isEqualTo("Order not found");
}
}
A full HTTP test with RestTestClient
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureRestTestClient
class ShopApplicationTests {
@Autowired
RestTestClient client;
@Test
void ordersEndpointWorksOverHttp() {
client.get().uri("/api/orders/1").header("API-Version", "2")
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.total.currency").isEqualTo("EUR");
}
}
Run the suite
./mvnw test
Testcontainers
A real database with @ServiceConnection
@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfig {
@Bean
@ServiceConnection
PostgreSQLContainer postgres() {
return new PostgreSQLContainer("postgres:18");
}
}
Scheduling & Async
@Scheduled, cron expressions and @Async
@Scheduled
Run a method on a schedule
@Configuration
@EnableScheduling
class SchedulingConfig {
}
@Component
class ReportJob {
@Scheduled(cron = "0 0 9 * * MON-FRI", zone = "Europe/Bucharest")
void dailyReport() {
}
@Scheduled(fixedDelayString = "${shop.sync-interval:5m}")
void syncStock() {
}
}
Spring cron has six fields
import org.springframework.scheduling.support.CronExpression;
import java.time.LocalDateTime;
var cron = CronExpression.parse("0 0 9 * * MON-FRI");
var t = LocalDateTime.of(2026, 10, 16, 10, 0);
for (int i = 0; i < 3; i++) {
t = cron.next(t);
System.out.println(t + " " + t.getDayOfWeek());
}
System.out.println(CronExpression.parse("@hourly").next(LocalDateTime.of(2026, 10, 16, 10, 20)));
Resilience
@Retryable and @ConcurrencyLimit in Spring Framework 7
Retries Without Extra Libraries
@Retryable with backoff
@SpringBootApplication
@EnableResilientMethods
public class ShopApplication {
}
@Component
public class InventoryClient {
@Retryable(includes = IllegalStateException.class, maxRetries = 3, delay = 100, multiplier = 2)
public int stockFor(String sku) {
return callInventoryService(sku);
}
}
Two failures, then success
java -jar target/shop-1.0.0.jar --spring.profiles.active=demo
Limit concurrent calls
@ConcurrencyLimit(10)
public Report render(long id) {
return renderer.render(id);
}
Virtual Threads
one property for Tomcat, executors and schedulers
Virtual Threads
Turn them on
spring.threads.virtual.enabled=true
What to watch
| Concern | Advice |
|---|---|
| connection pools | the database pool, not threads, becomes the limit; size it deliberately |
| pinning | since Java 24, synchronized no longer pins a virtual thread to its carrier, so the old advice to avoid it is mostly obsolete |
| ThreadLocal-heavy libraries | many short-lived threads multiply per-thread caches |
| CPU-bound work | gains nothing; virtual threads help with waiting, not computing |
Packaging & Docker
executable jars, layers, Dockerfiles and buildpacks
The Executable Jar
Build it and look inside
./mvnw -q package -Dmaven.test.skip=true ls -l --block-size=M target/shop-1.0.0.jar
The tools jar mode
java -Djarmode=tools -jar target/shop-1.0.0.jar help
Layers for container images
java -Djarmode=tools -jar target/shop-1.0.0.jar list-layers
Dockerfile
Multi-stage build with extracted layers
FROM eclipse-temurin:25-jre AS builder WORKDIR /builder COPY target/*.jar application.jar RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted FROM eclipse-temurin:25-jre WORKDIR /application COPY --from=builder /builder/extracted/dependencies/ ./ COPY --from=builder /builder/extracted/spring-boot-loader/ ./ COPY --from=builder /builder/extracted/snapshot-dependencies/ ./ COPY --from=builder /builder/extracted/application/ ./ ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "application.jar"]
No Dockerfile at all
./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=shop:1.0.0
Native Images & AOT
GraalVM native executables and their trade-offs
GraalVM Native Image
Build a native executable
./mvnw -Pnative native:compile ./target/shop
JVM or native
| Trait | JVM jar | Native executable |
|---|---|---|
| startup | seconds | tens of milliseconds |
| peak throughput | higher, thanks to the JIT compiler | lower without profile-guided optimization |
| build time | seconds | minutes, with several GB of RAM |
| profiles and @Conditional | evaluated at startup | fixed at build time |
| reflection and dynamic proxies | work | need hints for code Spring cannot see |
Upgrading to Boot 4
from Spring Boot 3.5 to 4.x, step by step
Before and After
The changes most applications hit
| Area | Spring Boot 4 |
|---|---|
| baseline | Java 17+, Jakarta EE 11, Servlet 6.1, Spring Framework 7, Kotlin 2.2+ |
| JSON | Jackson 3, packages tools.jackson; JsonMapper replaces ObjectMapper beans; @JsonComponent is now @JacksonComponent |
| starters | modular, with -test companions; spring-boot-starter-web is now spring-boot-starter-webmvc |
| tests | @MockBean and @SpyBean removed; MockMvc and TestRestTemplate need explicit @AutoConfigure... annotations |
| servers | Tomcat 11 or Jetty 12.1; Undertow support removed |
| actuator | management.endpoint.<id>.enabled replaced by .access |
| packaging | embedded launch scripts removed; layertools jar mode removed in 4.1 |
| null safety | JSpecify annotations across the Spring portfolio |
Let Boot report renamed properties
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
Jackson 2 as a stop-gap
spring.jackson.use-jackson2-defaults=true
Troubleshooting
the startup failures and errors people search for
Startup Failures
Port already in use
java -jar target/shop-1.0.0.jar
Messages and fixes
| Message | Cause and fix |
|---|---|
| Failed to configure a DataSource: 'url' attribute is not specified | a JDBC or JPA starter without a database URL or embedded database; set spring.datasource.url or add H2 for development |
| Parameter 0 of ... required a bean of type '...' that could not be found | the class is outside the scanned package, missing @Component, or its auto-configuration's condition failed; run with --debug |
| The dependencies of some of the beans in the application context form a cycle | two beans inject each other; move shared logic to a third bean |
| Binding to target ... failed | a property has the wrong type or fails validation; the report names its origin |
| Whitelabel Error Page | no controller matched and no error view exists; check the URL and the HTTP method |
| Web server failed to start. Port 8080 was already in use | see above |
Runtime Surprises
401 for a URL that does not exist
curl -s localhost:8080/nope
A primary key conflict after seeding data.sql
More symptoms
| Symptom | Usual cause |
|---|---|
| LazyInitializationException | entity fields loaded after the transaction ended; fetch them in the query |
| changes in application.properties ignored | the file is in the wrong folder, a profile file overrides it, or an environment variable wins |
| @Transactional, @Async or @Retryable does nothing | the method is called from the same class, or is private |
| JSON field names or dates look different after upgrading | Jackson 3 defaults; try spring.jackson.use-jackson2-defaults=true while migrating |
| slow startup | component scanning a large package, eager JPA validation, or too many auto-configurations; check the startup endpoint |
Versions
Spring Boot releases, support dates and managed dependencies
Release Line
Generations and open-source support
| Version | First release | OSS support until |
|---|---|---|
| 4.2 | planned for November 2026; 4.2.0-M2 on September 24, 2026 | December 31, 2027 |
| 4.1 | June 10, 2026; 4.1.1 on August 20, 2026 | July 31, 2027 |
| 4.0 | November 20, 2025; 4.0.8 on August 21, 2026 | December 31, 2026 |
| 3.5 | May 2025; 3.5.16 on June 25, 2026 | ended June 30, 2026 |
Inside Boot 4.1.1
Managed versions in the shop app
| Component | Version |
|---|---|
| Spring Framework | 7.0.9 |
| Spring Security | 7.1.1 |
| Spring Data JPA | 4.1.1 |
| Hibernate ORM | 7.4.5.Final |
| Hibernate Validator | 9.1.3.Final |
| Jackson | 3.1.5 |
| Tomcat | 11.0.24 |
| HikariCP | 7.0.2 |
| Micrometer | 1.17.1 |
| JUnit Jupiter | 6.0.3 |
| H2 | 2.4.240 |
What Is a Spring Boot Cheat Sheet
A Spring Boot cheat sheet is a compact reference to the parts of Spring Boot developers use every day: the starters and build setup, application.properties and profiles, the annotations behind dependency injection and REST controllers, Spring Data JPA, security, actuator and testing. People also search for a Spring Boot annotations cheat sheet or a Spring Boot quick reference; they usually want the same thing while building a service or upgrading one.
This cheat sheet covers 28 sections and 101 snippets for Spring Boot 4. The output under the examples comes from a real shop service built from start.spring.io on Spring Boot 4.1.1 and Java 25 and run on October 14, 2026: the startup log, every curl response, the failure messages, the conditions report and the test results.
The toolbox at the top checks an application.properties or application.yml file against the 201 properties that Spring Boot 4.0 and 4.1 renamed or removed, and flags risky settings. It also converts between properties and YAML, shows each key as an environment variable and a command-line argument, and explains Spring cron expressions with their next run times. The builders write a Maven or Gradle build file with the Boot 4 starters, an entity with its repository and REST controller, database configuration for six databases and a layered Dockerfile. Nothing you paste is sent anywhere.
How It Differs From the Reference Documentation
The Spring Boot reference documentation explains every feature in depth. This page puts the parts you look up during real work side by side and marks what changed in Boot 4, from the modular starters to the removal of @MockBean. For the API design side, see the REST API cheat sheet and the HTTP status codes cheat sheet; for the language itself, the Java cheat sheet.
What This Spring Boot Cheat Sheet Covers
The 28 sections follow the life of a service, and each link jumps to that section above.
- Start: the toolbox and a first app and project setup with Boot 4 starters.
- Configuration: properties and precedence, @ConfigurationProperties, profiles, beans and injection and auto-configuration.
- Web: REST controllers, validation, error handling, API versioning and HTTP clients.
- Data: Spring Data JPA, transactions and migrations.
- Production: security, actuator, logging and observability.
- Runtime: testing, scheduling, retries and virtual threads.
- Ship it: jars and Docker, native images, upgrading to Boot 4, troubleshooting and versions.
Spring Boot Quick Reference
The annotations and settings people look up most often, one line each.
| Task | Use |
|---|---|
| Main class | @SpringBootApplication |
| REST endpoint | @RestController, @GetMapping("/{id}") |
| Read the request | @PathVariable, @RequestParam, @RequestBody |
| Validate input | @Valid plus @NotBlank, @Min |
| Typed settings | @ConfigurationProperties("shop") on a record |
| Change the port | server.port=8081 or SERVER_PORT=8081 |
| Activate a profile | --spring.profiles.active=prod |
| Repository | interface OrderRepository extends JpaRepository<Order, Long> |
| Transaction | @Transactional on a public service method |
| Error body | ProblemDetail from @RestControllerAdvice |
| Mock a bean in tests | @MockitoBean |
| Health check | GET /actuator/health |
What Spring Boot Is
Spring Boot is an open-source framework from the Spring team, now part of Broadcom, for building production-ready Java and Kotlin applications on top of the Spring Framework. Version 1.0 shipped in 2014; Spring Boot 4.0 followed on November 20, 2025, and the current release, 4.1.1, on August 20, 2026.
Boot makes three decisions for you. Auto-configuration sets up components based on what is on the classpath, such as a DataSource when a JDBC driver is present. Starters bundle compatible dependency versions. And the build plugins package the application with an embedded server into one jar that runs with java -jar, in a container or as a native executable.
Spring Boot is the most common choice for Java back-end development, from monoliths to microservices, and a frequent target of API integration work in enterprises.
Where It Fits
Spring Boot suits teams that want a mature, well-documented stack with first-class support for databases, security, messaging and observability. Lighter frameworks such as Quarkus and Micronaut compete on startup time and memory, a gap that Boot's native image support and virtual threads narrow considerably.
How a Request Flows Through Spring Boot
Following one request through the shop service explains where each part of this page applies.
- Tomcat accepts the connection and, with virtual threads enabled, hands the request to a new virtual thread.
- The Spring Security filter chain authenticates the caller and checks the rules, answering 401 or 403 when they fail.
- The DispatcherServlet picks a controller method by path, HTTP method and API version.
- Arguments are bound from the path, query and JSON body, and @Valid runs Bean Validation.
- The controller calls a service; @Transactional opens a transaction around the repository calls.
- Spring Data JPA runs the query through Hibernate and HikariCP.
- Jackson 3 writes the result as JSON, or @RestControllerAdvice turns an exception into a problem detail, and Micrometer records the timing.
Common Spring Boot Mistakes
The same problems show up in most code reviews of Spring Boot services.
In Configuration
- Leaving spring.jpa.open-in-view at its default of true.
- Using ddl-auto=update against a shared database instead of Flyway or Liquibase.
- Exposing every actuator endpoint with exposure.include=* on a public port.
- Committing passwords in application.properties instead of reading them from the environment.
- Field injection with @Autowired, which hides dependencies and complicates tests.
In Code
- Calling a @Transactional, @Async or @Retryable method from the same class and expecting the annotation to apply.
- Returning JPA entities straight from controllers, which couples the API to the table and triggers lazy-loading errors.
- Catching exceptions in controllers instead of mapping them once in @RestControllerAdvice.
- HTTP clients without timeouts.
- Testing everything with @SpringBootTest instead of faster slices such as @WebMvcTest and @DataJpaTest.
How to Build Good Spring Boot Services
Keep the Layers Honest
Controllers translate HTTP, services hold business rules and transactions, repositories talk to the database. Keep configuration typed with @ConfigurationProperties records, validate it at startup, and let every environment override only what differs. Run the build and tests in continuous integration on every change.
Then Test and Watch It
Use test slices for speed and Testcontainers for the database you run in production, as the guide to types of software testing describes. Expose health and metrics through actuator, ship structured logs and traces, and check them after each deployment. The Docker cheat sheet and Kotlin cheat sheet cover the container and language side.
How This Spring Boot Cheat Sheet Is Maintained
This page is written and maintained by Bogdan Sandu for TMS Outsource, a software development company. It was checked on October 14, 2026 against the Spring Boot reference documentation, the Spring Boot 4.0 migration guide, the 4.0 and 4.1 release notes and configuration changelogs, and Spring's support timeline. The output of the examples was produced by running a Spring Boot 4.1.1 application on Eclipse Temurin 25 with Maven 3.10, curl and jshell. If a snippet is wrong or out of date, the date in the byline shows when it was last reviewed.
Spring Boot Questions People Actually Ask
The questions developers search for most about Spring Boot, answered in a few sentences each.
What is Spring Boot?
Spring Boot is a framework for building Java and Kotlin applications on top of the Spring Framework with almost no setup. It configures components automatically based on the libraries on the classpath, embeds a web server such as Tomcat, and packages the application as one executable jar, so a REST service runs with java -jar.
What is new in Spring Boot 4?
Spring Boot 4.0, released on November 20, 2025, moved to Spring Framework 7, Jakarta EE 11 and Jackson 3, split auto-configuration into modular starters with test companions, added API versioning, HTTP service client configuration, RestTestClient and an OpenTelemetry starter, and removed Undertow support and @MockBean. Spring Boot 4.1 followed on June 10, 2026.
Which Java version does Spring Boot 4 need?
Spring Boot 4 requires Java 17 or later and recommends the latest LTS release, which is Java 25. Kotlin 2.2 or later is supported, and native images need GraalVM 25 or later.
What is the difference between application.properties and application.yml?
Both hold the same configuration; YAML nests keys and suits lists, while .properties is flat and simpler to override. Boot reads either, and if both exist in the same location, the .properties file takes precedence.
How do I change the port in Spring Boot?
Set server.port=8081 in application.properties, pass --server.port=8081 on the command line, or set the SERVER_PORT environment variable. server.port=0 picks a random free port, which is useful in tests.
What does @SpringBootApplication do?
It combines @Configuration, @EnableAutoConfiguration and @ComponentScan. The class is the configuration root, auto-configuration is switched on, and every component in its package and subpackages is registered as a bean.
What replaced @MockBean in Spring Boot 4?
@MockBean and @SpyBean were removed in Spring Boot 4. Use @MockitoBean and @MockitoSpyBean from Spring Framework, which work on test class fields and can also be placed on the test class with a types attribute.
How do I upgrade from Spring Boot 3 to 4?
Upgrade to the latest 3.5.x and remove deprecation warnings, then switch to Boot 4, rename starters such as spring-boot-starter-web to spring-boot-starter-webmvc, add the test starters, migrate Jackson code to the tools.jackson packages, replace @MockBean, and add spring-boot-properties-migrator to find renamed properties.
Is this Spring Boot cheat sheet up to date?
Yes. It was checked on October 14, 2026 against Spring Boot 4.1.1, Spring Framework 7.0.9, Spring Security 7.1.1, the Boot 4.0 migration guide and the 4.0 and 4.1 configuration changelogs. The example outputs come from a real Boot 4.1.1 application running on Java 25.
Can I download this Spring Boot cheat sheet as a PDF?
Yes. The page has a print stylesheet, so your browser's print command with Save as PDF produces a clean copy with every output and explanation expanded and the navigation left out. The print core button prints only the core snippets for a short reference.