Back to Articles
Platform EngineeringMar 20269 min read

Migrating 70+ Microservices to Java 21: Hard Lessons in Dependency Governance

Standardizing Spring Boot 3, virtual threads, Jakarta namespaces, and SAST/DAST pipelines


#The Technical Debt Ingestion

Over years of fast-paced startup scaling, our microservices drifted across versions. Some ran on Java 8, others on Java 11, with Spring Boot versions ranging from 2.1 to 2.7. This drift made it impossible to share utility libraries, created security review bottlenecks, and blocked us from taking advantage of modern runtime efficiencies.

Java 21 Virtual Threads Platform Migration

// Java 21 Upgrade Blueprint: Transitioning traditional blocking JRE threads to virtual threads under Spring Boot 3.

#Standardizing with Maven Parent POMs

To execute the migration without individual service customization, we introduced a centralized **Platform Parent POM**. This parent POM standardizes dependency versions, compiler configurations, security plugins, and container build properties across all services.

xmlRead-Only
<!-- Platform Parent POM snippet standardizing compilation and security -->
<project>
    <groupId>com.platform.core</groupId>
    <artifactId>platform-parent-pom</artifactId>
    <version>2026.1.0</version>
    <packaging>pom</packaging>

    <properties>
        <java.version>21</java.version>
        <spring-boot.version>3.2.4</spring-boot.version>
        <owasp.dependency-check.version>9.0.9</owasp.dependency-check.version>
    </properties>

    <build>
        <plugins>
            <!-- Enforce Java 21 compilation -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>21</source>
                    <target>21</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

#Taming the Jakarta Namespace Transition

The largest breaking change in Spring Boot 3 was the migration from `javax.*` to `jakarta.*` packages. This affected import statements for databases (JPA), validation, and web filters. We automated ~90% of the namespace refactoring using OpenRewrite recipes, running scripts across all service repositories to rewrite imports automatically in bulk.

#Leveraging Virtual Threads (Project Loom)

One of our main business objectives was to improve throughput on I/O-heavy services. Under the thread-per-request model, standard Tomcat servlet containers would block active threads waiting on database queries. By enabling virtual threads in Spring Boot, we unblocked this boundary.

propertiesRead-Only
# Enable Java 21 Virtual Threads in Spring Boot 3.2+
spring.threads.virtual.enabled=true

With virtual threads enabled, memory consumption on high-throughput gateway nodes dropped by 40%, and maximum request capacity increased by over 2.5x under synthetic I/O load tests.

Have questions about this pattern?

If you want to discuss authentication mechanisms, database scaling bottlenecks, or security automation in distributed platforms, let's schedule an engineering talk.

Get in Touch