Migration to Java 25

Migration to Java 25

Please add any missing hints to this document!

See TC's approved Officially Supported Technologies document for the target release:

Help

Ask on the #development Slack channel if a module doesn't build on Java 25.


GitHub Actions

Most modules use the centralized maven.yml workflow (README-maven.md). Go to the
uses: folio-org/.github/.github/workflows/maven.yml@v1
entry and add

with: java-version: '25'

to switch the java version, and you are done!

Delete

docker-health-command: 'wget --no-verbose --tries=1 --spider http://localhost:8081/admin/health || exit 1'

because the Docker Hardened Images (to be approved by TC) don’t ship with wget for security reasons. Instead add an integration test for the mvn verify phase that tests the container. Examples: mod-permissions PermsIT, mod-oai-pmh ModTenantApiIt, mod-inventory InventoryIT.

The following applies only to modules that use their own GitHub Action Workflows:

All modules build via GitHub Actions. Change java-version from '21' to '25' in every .github/workflows/*.yml file:

- uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '25' cache: maven

Also update the Maven dependency cache key to avoid cache collisions between JDK 21 and JDK 25 build runs:

- uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-jdk25-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-jdk25-maven-

Dockerfile

Umbrellaleaf | Policy: Unspecified Versions suggests to use Docker Hardened Images (dhi.io/eclipse-temurin:25-alpine), however, TC hasn’t approved it yet. Use this Dockerfile, adjust the port number as needed:

FROM docker.io/folioci/eclipse-temurin:25-alpine WORKDIR /app COPY ./target/*.jar app.jar EXPOSE 8081 CMD ["java", "-jar", "app.jar"]

If there are multiple .jar files in the target directory and we only need the -fat.jar file use this Dockerfile, adjust the port number as needed:

FROM docker.io/folioci/eclipse-temurin:25-alpine WORKDIR /app COPY ./target/*-fat.jar app.jar EXPOSE 8081 CMD ["java", "-jar", "app.jar"]

You should use a .dockerignore file, for examples see https://github.com/folio-org/folio-tools/tree/master/folio-java-docker/openjdk21#sample-dockerignore-file

If not using Docker Hardened Images (to be confirmed by TC):

Change the base image from eclipse-temurin:21 to eclipse-temurin:25:

# Before FROM folioci/alpine-jre-openjdk21:latest # After FROM eclipse-temurin:25-jre-alpine

For details see the folio-java-docker repository.

Note on Alpine: If a module uses native libraries that require glibc, use a multi-stage build and install gcompat. If it doesn’t work: Before switching to eclipse-temurin:25-jre (Debian slim) consult FOLIO Security Team.


POM Files

In the <properties> block change the java.version to 25:

<java.version>25</java.version>

In the <properties> block:

  • delete the <maven.compiler.source> entry

  • delete the <maven.compiler.target> entry

Also ensure the maven-compiler-plugin

  • has version 3.13.0 or later

  • uses <release> in the <configuration> section

  • does not use <source> in the <configuration> section

  • does not use <target> in the <configuration> section:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.16.0</version> <configuration> <release>${java.version}</release> </configuration> </plugin>

If using lombok also add <annotationProcessorPaths> to maven-compiler-plugin configuration, otherwise you get “incompatible types: … cannot be converted to lombok.val” or “variable … not initialized in the default constructor” compile error:

<plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.16.0</version> <configuration> <release>${java.version}</release> <encoding>UTF-8</encoding> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin>

If using jacoco-maven-plugin bump to version 0.8.15 or later.

Upgrade the Maven Wrapper to Maven 3.9.8+:

mvn wrapper:wrapper -Dmaven=3.9.8

Breaking Changes

SecurityManager removed (JEP 486)

java.lang.SecurityManager is permanently disabled in JDK 24+. All related APIs throw UnsupportedOperationException at runtime.

Search your module for any direct usage:

grep -r "SecurityManager\|AccessController\|doPrivileged" src/

Most modules are unaffected — the risk is in transitive dependencies using old framework versions. Upgrading Spring Boot to 3.5.x (see below) resolves the most common cases.

If you find AccessController.doPrivileged() in your own code, replace it with a direct call — the privileged block pattern has no effect without a SecurityManager.


Non-generational ZGC removed (JEP 490)

The JVM flag -XX:-ZGenerational no longer exists in JDK 24+. Any JAVA_OPTS containing it will cause JVM startup failure.

Check your module's environment configuration:

grep -r "ZGenerational" .

Remove the flag wherever found. The safe replacement for JDK 25:

JAVA_OPTS="-XX:+UseZGC -Xmx512m"

Note: Helm charts and Kubernetes values.yaml files are handled by the DevOps team — raise a ticket if you find these in your module's infrastructure files.


sun.misc.Unsafe warnings (JEP 498)

Direct use of sun.misc.Unsafe memory-access methods now emits runtime warnings and is targeted for removal in a future JDK. This is most commonly introduced via older library versions.

The dependency upgrades below (especially Spring Boot 3.5.x and Netty 4.1.100+) resolve the majority of these warnings automatically.


Spring Boot

folio-spring-support is already on Spring Boot 4.1.1 (Spring Framework 7.x). Modules that inherit from it only need the Java version bump — no Spring Boot upgrade is required for those modules.

Check which category your module falls into:

# Does your module depend on folio-spring-support? mvn dependency:tree | grep folio-spring-support

Modules using folio-spring-support (majority of mod-* modules)

Spring Boot 4.1.1 is already JDK 25-compatible. The only required change is bumping java.version to 25 in your pom.xml (covered in the POM Files section above).

No Spring Boot upgrade needed. Verify you are on folio-spring-support 10.x or later:

<dependency> <groupId>org.folio</groupId> <artifactId>folio-spring-base</artifactId> <version>10.1.0</version> <!-- or latest 10.x --> </dependency>

Notable Spring Boot 4.x items to be aware of

The Spring Boot 4.0 Migration Guide describes breaking changes from 3.x. If your module was previously migrated from Spring Boot 3.x to 4.x as part of folio-spring-support adoption, these should already be resolved. Double-check:

  • @MockBean → @MockitoBean, @SpyBean → @MockitoSpyBean in all tests.

  • management.endpoints.access.default replaces management.endpoints.enabled-by-default (Boolean → none / read-only / unrestricted).

  • Virtual threads are enabled by default in Spring Boot 4.x for Tomcat/Jetty. Review any code that assumes platform-thread identity or uses ThreadLocal for per-request state.

  • spring-cloud-starter-openfeign — use the Spring Boot 4.x-compatible release. Check the latest tag at https://github.com/spring-cloud/spring-cloud-openfeign/tags.


Modules NOT using folio-spring-support (standalone Spring Boot modules)

If your module manages its own Spring Boot parent independently, upgrade spring-boot-starter-parent to at least 3.5.x, or align with the platform by upgrading to 4.1.x. Find the latest tags at https://github.com/spring-projects/spring-boot/tags.

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>4.1.1</version> <!-- recommended: align with folio-spring-support --> </parent>

Consult the Spring Boot 3.5 Release Notes and Spring Boot 4.0 Migration Guide for the full set of changes if jumping from 3.x to 4.x.

Lombok

See POM Files section above.

Mockito

Mockito 5.x is required for JDK 25 compatibility. It pulls in ByteBuddy 1.14+ which removes direct sun.misc.Unsafe usage.

<properties> <argLine></argLine> <mockito.version>5.16.0</mockito.version> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.5.2</version> <configuration> <argLine> @{argLine} -javaagent:${settings.localRepository}/org/mockito/mockito-core/${mockito.version}/mockito-core-${mockito.version}.jar -Xshare:off </argLine> </configuration> </plugin> </plugins> </build>

See the Mockito Javadoc for inline-mock-maker instrumentation details if tests fail.


Jackson

Remove jackson-databind-afterburner if declared — it uses sun.misc.Unsafe and is incompatible. Replace with jackson-module-blackbird or remove entirely (the standard Jackson serialiser is sufficient for most FOLIO use cases):

<!-- Remove this --> <dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-afterburner</artifactId> </dependency> <!-- Add this if needed for performance --> <dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-blackbird</artifactId> </dependency>

Upgrade jackson-databind to 2.17.0 or later.


Vert.x based modules

Modules using Vert.x fall into two groups depending on which FOLIO library they use. In both cases the underlying Vert.x runtime is already on 5.x — only the Java version bump is required.

Check which library your module uses:

mvn dependency:tree -Dincludes=org.folio:vertx-lib,org.folio:domain-models-runtime

folio-vertx-lib based modules

folio-vertx-lib is currently on Vert.x 5.1.5 and Java 21 (version 4.2.0-SNAPSHOT). It already has maven-jdeps-plugin configured in the build.

For modules depending on folio-vertx-lib, only the Java version bump is needed:

<!-- In maven-compiler-plugin configuration --> <release>25</release>

Upgrade to the latest folio-vertx-lib 4.x release to ensure you are on Vert.x 5.1.x:

<dependency> <groupId>org.folio</groupId> <artifactId>folio-vertx-lib</artifactId> <version>4.x.x</version> <!-- use latest released version --> </dependency>

Note: If your module is still on Vert.x 4.x directly (not via folio-vertx-lib), you must upgrade to Vert.x 5.x first. Vert.x 5 is a major version with breaking API changes — see the Vert.x 5 migration guide before proceeding.

Native transport and JNI warnings (JEP 472)

Vert.x modules that use native transports (epoll, io_uring on Linux) will emit JNI restriction warnings on JDK 24+ because Netty loads native libraries via System.loadLibrary(). Add --enable-native-access=ALL-UNNAMED to JAVA_OPTS to suppress:

JAVA_OPTS="-XX:+UseZGC -Xmx512m --enable-native-access=ALL-UNNAMED"

Or remove the native transport dependency if your module does not require it for performance — for most FOLIO edge modules the NIO transport is sufficient.

Event loop model vs virtual threads

Vert.x 5.x supports virtual threads via explicit opt-in (VertxOptions configuration). The event-loop model remains the default and is unaffected by JDK 25. Do not enable virtual threads unless the module has been tested with them.

Hazelcast clustering (if used)

If a module uses vertx-hazelcast for cluster management, verify Hazelcast is on 5.x:

mvn dependency:tree | grep hazelcast

vertx-hazelcast >= 5.0.0 automatically ships with Hazelcast 5: https://github.com/vert-x3/vertx-hazelcast/commit/ed36d7598ade718efa60b1035e051f9c206bd982

Most FOLIO Vert.x modules do not use clustering — skip if vertx-hazelcast is not in the dependency tree.


RMB (raml-module-builder) based modules

RMB is currently on Vert.x 5.1.6 and Java 21 (version 36.1.0-SNAPSHOT).

For modules depending on RMB, only the Java version bump is required — RMB already manages Vert.x 5.x and all compatible dependencies transitively.

Upgrade to the latest RMB 36.x release:

<dependency> <groupId>org.folio</groupId> <artifactId>domain-models-runtime</artifactId> <version>36.x.x</version> <!-- use latest released version --> </dependency>

Then bump the compiler target in your module's pom.xml:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <release>25</release> </configuration> </plugin>

RMB-specific notes:

  • RMB uses AspectJ (1.9.22.1) for AOP. The aspectj-maven-plugin and aspectjrt are compatible with JDK 25 at this version — no changes needed.

  • RMB still uses javax.ws.rs-api (old javax namespace, not jakarta). This is a known RMB characteristic and is unrelated to the JDK 25 upgrade — do not attempt to migrate to jakarta.ws.rs as part of this work.

  • RMB uses Micrometer (1.17.0) for metrics. This version is compatible with JDK 25.

  • The JNI / native transport note above applies equally to RMB-based modules.

Verify RMB upgrade with:

mvn clean verify -Pintegration-test

--add-opens flags

Review any --add-opens or --add-exports flags in your pom.xml, build.gradle, or test runner configuration. Some of these were added as workarounds for earlier JDK versions and may no longer be required. Remove any that are obsolete; document any that must remain.


Migrated repositories

FOLIO-4565 – Epic: Upgrade to Java 25 and Spring Boot 3.5

Update this section with proof-of-concept migration PRs as they are completed.


Dependency version targets

Library

Current version in FOLIO

Action for JDK 25

Notes

Library

Current version in FOLIO

Action for JDK 25

Notes

folio-spring-support

10.1.0-SNAPSHOT (Spring Boot 4.1.1)

Bump java.version to 25 only

Spring Boot 4.1.1 is JDK 25-compatible

folio-vertx-lib

4.2.0-SNAPSHOT (Vert.x 5.1.5)

Bump compiler <release> to 25 only

Already on Vert.x 5.x

raml-module-builder (RMB)

36.1.0-SNAPSHOT (Vert.x 5.1.6)

Bump compiler <release> to 25 only

Already on Vert.x 5.x; uses javax (not jakarta)

Spring Boot (standalone modules)

—

4.1.1 recommended, 3.5.x minimum

Align with folio-spring-support if possible

Vert.x (direct dependency)

—

5.1.x

4.x is incompatible — upgrade to 5.x first

spring-cloud-starter-openfeign

—

Spring Boot 4.x-compatible release

Check https://github.com/spring-cloud/spring-cloud-openfeign/tags

Mockito

5.23.0 (in folio-vertx-lib / RMB)

Already compatible

folio-spring-support modules: use 5.16.0+

Jackson Databind

—

2.17.0+

 

Jackson Afterburner

—

Remove

Replace with blackbird or remove

Testcontainers

2.0.4 (in folio-vertx-lib / RMB); 2.0.5 (in Spring Boot 4.1)

Already compatible

 

Liquibase

—

4.27.0+

Spring Boot modules only

Flyway

—

10.10.0+

Spring Boot modules only

Bouncy Castle

—

1.78+

 

Hazelcast (Vert.x clustering only)

—

5.x

Check with mvn dependency:tree | grep hazelcast