Build Spring Boot 4.0 applications - project setup, REST controllers, dependency injection, configuration, actuator, and testing
91
90%
Does it follow best practices?
Impact
97%
1.79xAverage score across 3 eval scenarios
Passed
No known issues
| Component | Version |
|---|---|
| Spring Boot | 4.0.x (GA 2025-11-20, current 4.0.5) |
| Spring Framework | 7.0.6+ |
| Java | 17+ (recommended: latest LTS, first-class support for Java 25) |
| Jakarta EE | 11 (Servlet 6.1, Persistence 3.2, Validation 3.1, WS RS 4.0) |
| Tomcat | 11.0.x |
| Jetty | 12.1.x |
| Jackson | 3.0 (group ID: tools.jackson, annotations remain com.fasterxml.jackson.core) |
| Kotlin | 2.2+ |
| Gradle | 8.14+ or 9.x |
| Maven | 3.6.3+ |
Undertow is NOT supported in Boot 4 (no Servlet 6.1 compat).
Follow these steps to get a new Spring Boot 4 project running:
spring-boot-starter-webmvc and any other starters needed.mvn dependency:tree (or gradle dependencies). If Jackson conflicts appear (mixing com.fasterxml.jackson and tools.jackson artifacts), see the Jackson 3.0 section before proceeding.@SpringBootApplication and call SpringApplication.run.spring.application.name and server.port./actuator/health if the actuator starter is present.@RestController, hit it with curl or a test, confirm a 200 response. If MockMvc tests fail, ensure @AutoConfigureMockMvc is present — @SpringBootTest no longer auto-configures it in Boot 4.com.fasterxml.jackson and tools.jackson artifacts; see Jackson 3.0 section.@AutoConfigureMockMvc — @SpringBootTest no longer auto-configures it in Boot 4.@MockBean compilation error: Replace with @MockitoBean / @MockitoSpyBean.management.tracing.enabled to management.tracing.export.enabled.<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
</dependencies>The web starter was renamed: spring-boot-starter-web still works but the canonical name is now spring-boot-starter-webmvc.
plugins {
java
id("org.springframework.boot") version "4.0.5"
id("io.spring.dependency-management") version "1.1.7"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-webmvc")
}spring.application.name=my-service
server.port=8080REST controller structure is unchanged from Boot 3. Boot 4-specific considerations:
spring.mvc.problemdetails.enabled=true:import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotFoundException.class)
public ProblemDetail handleNotFound(NotFoundException ex) {
ProblemDetail pd = ProblemDetail.forStatusAndDetail(
HttpStatus.NOT_FOUND, ex.getMessage());
pd.setTitle("Resource Not Found");
return pd;
}
}Enable RFC 9457 Problem Details globally: spring.mvc.problemdetails.enabled=true
HttpMessageConverters is deprecated — use ServerHttpMessageConvertersCustomizer and ClientHttpMessageConvertersCustomizer instead.spring.application.name=my-service
server.port=8080
server.servlet.context-path=/api
spring.jackson.json.write.indent-output=true
spring.jackson.json.read.use-big-decimal-for-floats=true
logging.level.com.example=DEBUGNote: Jackson property prefix changed — BOTH read and write paths moved:
spring.jackson.write.* → spring.jackson.json.write.*spring.jackson.read.* → spring.jackson.json.read.*Prefer records with @DefaultValue (immutable, no getters/setters needed):
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
@ConfigurationProperties("app.agent")
public record AgentProperties(
String name,
@DefaultValue("gpt-4") String model,
@DefaultValue("4096") int maxTokens,
@DefaultValue("30") int timeoutSeconds) {}Enable with @ConfigurationPropertiesScan on the main class or @EnableConfigurationProperties(AgentProperties.class).
Important: Use @DefaultValue on record components for defaults — NOT Java field initializers (records don't have mutable fields). The @DefaultValue annotation is from org.springframework.boot.context.properties.bind.
JavaBean classes with getters/setters also work but records are preferred in Boot 4.
Activate: spring.profiles.active=dev or --spring.profiles.active=prod
Profile-specific files: application-dev.properties, application-prod.yaml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.endpoint.health.show-details=always
management.endpoint.health.probes.enabled=true
management.server.port=9090Key endpoints: /actuator/health, /actuator/metrics, /actuator/info, /actuator/prometheus
Boot 4 change: Liveness/readiness probes are enabled by default (was opt-in in 3.x).
Boot 4 change: management.tracing.enabled renamed to management.tracing.export.enabled.
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class AgentHealthIndicator implements HealthIndicator {
@Override
public Health health() {
return Health.up()
.withDetail("agent", "operational").build();
}
}import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@Endpoint(id = "agent")
public class AgentEndpoint {
@ReadOperation
public Map<String, Object> info() {
return Map.of("status", "running", "version", "1.0");
}
}<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>New in Boot 4 — OpenTelemetry starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-opentelemetry</artifactId>
</dependency>Critical changes from Boot 3: @MockBean is removed, MockMvc is no longer auto-configured.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc // REQUIRED in Boot 4 — not auto-configured by @SpringBootTest
class ItemControllerTest {
@Autowired
private MockMvc mockMvc;
@MockitoBean // replaces @MockBean (removed in Boot 4)
private ItemService itemService;
@Test
void shouldReturnItems() throws Exception {
when(itemService.findAll()).thenReturn(List.of(new Item(1L, "Test")));
mockMvc.perform(get("/api/items"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").value("Test"));
}
}| Boot 3 | Boot 4 |
|---|---|
@MockBean | @MockitoBean |
@SpyBean | @MockitoSpyBean |
@SpringBootTest auto-configures MockMvc | Must add @AutoConfigureMockMvc |
org.springframework.lang.Nullable | org.jspecify.annotations.Nullable |
import org.jspecify.annotations.Nullable;
public class ItemService {
public @Nullable Item findById(Long id) {
return repository.findById(id).orElse(null);
}
}org.springframework.lang.Nullable is replaced by org.jspecify.annotations.Nullable in Boot 4. Update all imports.
| 3.x | 4.0 |
|---|---|
spring-boot-starter-web | spring-boot-starter-webmvc |
spring-boot-starter-web-services | spring-boot-starter-webservices |
spring-boot-starter-aop | spring-boot-starter-aspectj |
spring-boot-starter-oauth2-client | spring-boot-starter-security-oauth2-client |
com.fasterxml.jackson changed to tools.jackson (except annotations — com.fasterxml.jackson.core annotations remain)@JsonComponent changed to @JacksonComponent@JsonMixin changed to @JacksonMixinJsonObjectSerializer changed to ObjectValueSerializerJackson2ObjectMapperBuilderCustomizer changed to JsonMapperBuilderCustomizerspring.jackson.read.* / spring.jackson.write.* moved to spring.jackson.json.read.* / spring.jackson.json.write.*spring-boot-jackson2 (deprecated)org.springframework.lang.Nullable replaced by org.jspecify.annotations.Nullable@MockBean / @SpyBean removed — use @MockitoBean / @MockitoSpyBean@SpringBootTest no longer auto-configures MockMvc — add @AutoConfigureMockMvcHttpMessageConverters deprecated — use ServerHttpMessageConvertersCustomizer and ClientHttpMessageConvertersCustomizermanagement.tracing.enabled changed to management.tracing.export.enabled