Import Changes, API Adjustments & Compatibility Results
Migration summary from Spring Boot 3.5 → 4.0, including breaking changes, dependency updates, and fixes across Web, JPA, Jackson 3, GCP, Pub/Sub, Thymeleaf, and Testing.
🔁 Key Import & API Changes
1️⃣ RestTemplate Builder (Client API)
❌ Removed
org.springframework.boot.web.client.RestTemplateBuilder✅ Replaced With
org.springframework.boot.restclient.RestTemplateBuilder✅ Dependency Change
Spring Boot 3
spring-boot-starter-webSpring Boot 4
spring-boot-starter-webmvc
spring-boot-starter-restclient📌 Reason Spring Boot 4 separates responsibilities:
- Server → spring-boot-starter-webmvc
- Client → spring-boot-starter-restclient
2️⃣ EntityManager Factory Builder
❌ Removed
org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder✅ Replaced With
org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean📌 Reason Spring Boot 4 removes Boot-specific abstractions → use standard Spring Framework APIs.
3️⃣ Jackson Migration (🔥 Major Change)
❌ Removed (Explicit Jackson 2)
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;✅ Added (Boot 4 Default – Jackson 3)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>tools.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>✅ Boot 4 Now Uses
import tools.jackson.core.json.JsonReadFeature;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.JsonNode;
import tools.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import tools.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import tools.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import tools.jackson.dataformat.xml.XmlMapper;
import org.springframework.boot.json.JsonParseException;⚠️ Compatibility Note
Libraries such as springdoc still use Jackson 2 internally → ✔️ Fully compatible (can run in parallel)
🔧 ObjectMapper Migration
Spring Boot 3
this.objectMapper
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);Spring Boot 4 (Rebuild from existing)
this.objectMapper =
((JsonMapper) objectMapper)
.rebuild()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.configure(JsonReadFeature.ALLOW_SINGLE_QUOTES, true)
.build();Alternative (New Instance)
this.objectMapper =
JsonMapper.builder()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.configure(JsonReadFeature.ALLOW_SINGLE_QUOTES, true)
.build();📌 Jackson 3 Package Changes
Alternative (Direct ObjectMapper with builder-style API)
When you need a plain ObjectMapper without JsonMapper.builder(), use the mutate-style API:
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.JsonNode;
var mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);📌 In Jackson 3, ObjectMapper is still valid for simple use cases. The disable(...) / enable(...) methods work directly on the instance — no need to use configure(feature, false) as in Jackson 2.
👉 Important: Annotations (@JsonProperty, etc.) remain unchanged → ✔️ No changes required in model/POJO classes
🔍 Migration Tip
grep -r "com.fasterxml.jackson.databind.ObjectMapper" src/main/java4️⃣ HTTP Exception Constructor Change
❌ Old
new HttpMessageNotReadableException(message, cause);✅ New
new HttpMessageNotReadableException(message, (HttpInputMessage) null);📌 Reason Spring Framework 7 removed the Throwable constructor.
✅ ResponseEntity with Status Codes
Spring Boot 4 also introduces cleaner static factory methods for common HTTP status codes. The new ResponseEntity<>(null, HttpStatus.XXX) pattern should be replaced with the dedicated builder methods.
Spring Boot 3
ResponseEntity<List<ApplyBillCreditResponseVO>> responseEntity =
new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);Spring Boot 4
ResponseEntity<List<ApplyBillCreditResponseVO>> responseEntity =
ResponseEntity.badRequest().build();
ResponseEntity.internalServerError().build();📌 Common ResponseEntity Migration
5️⃣ ResponseEntity Test Constructor Fix
❌ Broken in Boot 4
new ResponseEntity<>(null, HttpStatus.OK);✅ Fixed
ResponseEntity.ok(null);or
new ResponseEntity<String>((String) null, HttpStatus.OK);6️⃣ Test Stack Migration
❌ Removed
spring-boot-starter-test✅ Replaced With
spring-boot-starter-webmvc-test✅ New Test Platform Versions
- JUnit 6
- Mockito 5
- AssertJ 3.27
- Awaitility 4.3
7️⃣ Lombok (⚠️ Build-Time Configuration Required)
✅ Dependency
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>✅ Maven Compiler Plugin Setup
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>✅ Spring Boot Plugin Exclusion
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>📌 Reason Lombok must run only at compile-time → not packaged into runtime artifact.
8️⃣ Google Cloud (Secret Manager + Storage)
✅ Dependencies
<!-- Google Cloud Dependencies -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-secretmanager</artifactId>
</dependency>⚠️ Jackson Conflict Fix
Exclude Jackson 2 XML module:
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-storage</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</exclusion>
</exclusions>
</dependency>✅ Dependency Management (BOM)
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-dependencies</artifactId>
<version>8.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>9️⃣ Pub/Sub / Spring Integration
❌ Spring Boot 3
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
</dependency>✅ Spring Boot 4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>📌 Notes
- spring-boot-starter-integration auto-configures integration
- @EnableIntegration becomes available
- Required for Pub/Sub + Spring Integration flows
Pub/Sub Dependency
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-pubsub</artifactId>
</dependency>🔟 Testcontainers Migration
Spring Boot 3
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>oracle-free</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>gcloud</artifactId>
</dependency>Spring Boot 4
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-oracle-free</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-gcloud</artifactId>
</dependency>1️⃣1️⃣ Thymeleaf (⚠️ Known Issue in Boot 4)
Issue
org.thymeleaf:thymeleaf-spring6 → compatibility issue with Spring Framework 7✅ Workaround (Manual Bean)
@Configuration
public class ThymeleafManualConfig {
@Bean(name = "manualTemplateEngine")
@Primary
public TemplateEngine manualTemplateEngine() {
ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
resolver.setPrefix("templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCharacterEncoding("UTF-8");
resolver.setCacheable(false);
resolver.setCheckExistence(true);
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(resolver);
return engine;
}
}⚠️ Important
Avoid bean conflict:
templateEngine already exists → disable or rename1️⃣2️⃣ Test (Jackson Mock Change)
Spring Boot 3
when(jsonMapperBuilder.enable(any(DeserializationFeature.class)))
.thenReturn(jsonMapperBuilder);
when(jsonMapperBuilder.enable(any(JsonParser.Feature.class)))
.thenReturn(jsonMapperBuilder);Spring Boot 4
import tools.jackson.databind.json.JsonMapper;
@Mock
private JsonMapper objectMapper;
JsonMapper realMapper = JsonMapper.builder().build();1️⃣3️⃣ Springdoc OpenAPI (⚠️ Jackson 2 Compatibility)
✅ Dependency (Spring Boot 4 Supported Version)
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>3.0.2</version>
</dependency>⚠️ Important Behavior
- Spring Boot 4 → uses Jackson 3 (tools.jackson.*)
- Springdoc (3.0.2) → still uses Jackson 2 (com.fasterxml.*) internally
1️⃣4️⃣ JavaTimeModule in Jackson 3
⚠️ Important Clarification
In Jackson 3, JavaTimeModule no longer needs to be registered explicitly. The Jackson 3 migration guide states that the former Java 8 modules are now built into jackson-databind, including jackson-datatype-jsr310 for java.time support.
✅ What this means
Old Jackson 2 style
mapper.registerModule(new JavaTimeModule());Spring Boot 4 / Jackson 3
this.objectMapper =
JsonMapper.builder()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();📌 Result
- JavaTimeModule is not required anymore in Jackson 3.
- java.time support remains available out of the box.
- This specifically applies to: LocalDate LocalDateTime Instant other Java 8 date/time types.
💡 Practical Note
If your DTOs already serialize time values as String (for example Instant.now().toString()), then removing new JavaTimeModule() does not change that payload behavior.
📌 Jackson 3 embeds the former Java 8 modules directly into jackson-databind, including JSR-310 support for java.time types, so JavaTimeModule no longer needs to be registered explicitly.
1️⃣5️⃣ HttpHeaders Change in Spring Framework 7
⚠️ Breaking Change
In Spring Framework 7, HttpHeaders no longer implements the MultiValueMap contract. The official Javadoc and Spring Framework 7 release notes both call this out explicitly.
❌ Old style
HashMap<String, List<String>> customHeaders = new HashMap<>(request.getHeaders());
builder.append(" Custom Headers: ").append(customHeaders);✅ Updated style
HashMap<String, List<String>> customHeaders = new HashMap<>();
request.getHeaders().forEach(customHeaders::put);
builder.append(" Custom Headers: ").append(customHeaders);📌 Why this change is needed
Since Spring Framework 7, HttpHeaders is no longer map-like in the same way as before, and several Map / MultiValueMap style usages are no longer valid or are discouraged. Spring’s Javadoc also notes that asMultiValueMap() is now only for backward compatibility and should generally be avoided.
✅ Other common fixes
Check existence
headers.containsHeader("My-Header")instead of:
headers.containsKey("My-Header")Spring Framework 7 introduced header-focused alternatives because HttpHeaders no longer extends MultiValueMap.
📌 Spring Framework 7 changed HttpHeaders so it no longer implements MultiValueMap directly. Map-style usage such as new HashMap<>(request.getHeaders()) may fail and should be replaced with header-focused access patterns like request.getHeaders().forEach(...) or containsHeader(...).
1️⃣6️⃣ Logback — Transitive via spring-boot-starter-webmvc
✅ Dependency Tree
spring-boot-starter-webmvc already brings Logback transitively:
spring-boot-starter-webmvc
└─ spring-boot-starter
└─ spring-boot-starter-logging
├─ logback-classic
└─ logback-core Spring Boot already provides:
- logback-classic
- logback-core
✅ Safe to Remove
The following explicit dependencies are redundant and can be safely removed from pom.xml:
<!-- ❌ Remove — already provided transitively by spring-boot-starter-webmvc -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>📌 Declaring these explicitly adds no value and may cause version conflicts if the explicit version differs from the one managed by the Spring Boot BOM. Let Spring Boot manage the Logback version automatically.
1️⃣7️⃣ Spring Retry → Spring Resilience (Spring Framework 7 / Spring Boot 4)
⚠️ Breaking Change
✅ Dependencies — Safe to Remove
The following dependencies are no longer needed (Spring Boot 4 includes resilience support built-in):
<!-- ❌ Remove — replaced by Spring Framework 7 built-in resilience -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>✅ Configuration Class Migration
Spring Boot 3 (spring-retry)
import org.springframework.retry.annotation.EnableRetry;
@Configuration
@EnableRetry
public class RetryConfig {
}Spring Boot 4 (Spring Resilience)
import org.springframework.context.annotation.Configuration;
import org.springframework.resilience.annotation.EnableResilientMethods;
@Configuration
@EnableResilientMethods
public class RetryConfig {
}✅ @Retryable Annotation Migration
Spring Boot 3 (spring-retry)
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
@Retryable(
retryFor = {RuntimeException.class, Exception.class, HttpClientErrorException.Unauthorized.class},
exclude = {HttpClientErrorException.class},
maxAttempts = 4,
backoff = @Backoff(delay = 1000, multiplier = 2)
)Spring Boot 4 (Spring Resilience)
import org.springframework.resilience.annotation.Retryable;
@Retryable(
includes = {RuntimeException.class, Exception.class, HttpClientErrorException.Unauthorized.class},
excludes = {HttpClientErrorException.class},
maxRetries = 4,
delay = 1000,
multiplier = 2
)📌 Summary of Changes
1️⃣8️⃣ UriComponentsBuilder — fromHttpUrl Removed (Spring Framework 7)
⚠️ Breaking Change
❌ Removed Factory Methods
UriComponentsBuilder.fromHttpUrl(...)
UriComponentsBuilder.fromHttpRequest(...)
// etc.✅ Replacement
UriComponentsBuilder.fromUriString(...)🔁 Migration Example
Spring Boot 3
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(uri.replace("{my_param}", myDto.getMyField()));Spring Boot 4
UriComponentsBuilder builder = UriComponentsBuilder
.fromUriString(uri.replace("{my_param}", myDto.getMyField()));🧠 Why This Changed
Spring removed multiple confusing factory methods (fromHttpUrl, fromHttpRequest, etc.) and unified everything into fromUriString(...), which works for all URI types — HTTP, HTTPS, and generic URIs.
📌 Summary
1️⃣9️⃣ Trailing Slash Handling — UrlHandlerFilter (Spring Framework 7 / Spring Boot 4)
⚠️ Breaking Change
❌ Old Style (Spring Boot 3)
// Additional configuration to accept with and without forward slash /
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseTrailingSlashMatch(true);
}✅ New Style (Spring Boot 4)
@Bean
public UrlHandlerFilter urlHandlerFilter() {
return UrlHandlerFilter
.trailingSlashHandler("/my_url/**")
.wrapRequest()
.build();
}🧠 Why wrapRequest() Is the Right Choice
- wrapRequest() — wraps the incoming request to strip the trailing slash, behaving closest to the legacy setUseTrailingSlashMatch(true) without issuing HTTP redirects
- redirect() — would send a 301/308 redirect to the client, which changes the HTTP method and adds a round-trip
📌 Summary
✅ Final Conclusion
✔️ The full platform is fully functional on Spring Boot 4.0:
- Web (Spring MVC)
- JPA / Hibernate
- Jackson 3
- Pub/Sub + Spring Integration
- GCP Secret Manager
- Testcontainers
- Lombok (compile-time only)
- Observability / Actuator
👉 After applying all fixes → no runtime issues
💡 Final Insight
Spring Boot 4 is not just an upgrade — it’s a platform evolution:
- Clear separation of responsibilities
- Reduced framework magic
- Stronger dependency control
- Future-ready ecosystem (Java 21, Java 25 +, Framework 7)
🔗 References
Here are the official docs and community discussions used during the migration:
📘 Spring Boot & Framework
- Spring Boot 4.0 Migration Guide https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide
- Spring Framework 7 Reference https://docs.spring.io/spring-framework/reference/
- Spring Framework 7 HttpHeaders Javadoc https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/HttpHeaders.html
- Spring Framework 7 Release Notes https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-7.0-Release-Notes
☁️ Google Cloud (GCP)
- Spring Cloud GCP Repository https://github.com/GoogleCloudPlatform/spring-cloud-gcp
🔄 Jackson 3
- Jackson Project (GitHub) https://github.com/FasterXML/jackson
- Jackson 3 Migration Guide https://github.com/FasterXML/jackson/blob/main/jackson3/MIGRATING_TO_JACKSON_3.md
- Jackson 3 Release Notes https://github.com/FasterXML/jackson/wiki/Jackson-Release-3
🔌 Spring Integration
- Spring Integration Reference https://docs.spring.io/spring-integration/reference/overview.html
- What’s New in Spring Integration https://docs.spring.io/spring-integration/reference/whats-new.html#whats-new-part
- Spring Integration 7 Release Blog https://spring.io/blog/2025/11/19/spring-integration-7-0-0-released
- Spring Boot Integration Docs https://docs.spring.io/spring-boot/reference/messaging/spring-integration.html
💬 Community Discussion
- Stack Overflow – Boot 4 + GCP Issue https://stackoverflow.com/questions/79838205/spring-boot-4-0-0-gcp-java-lang-nosuchmethoderror-org-springframework-boot