Skip to content

TECH_DEBT: Updating Java and Spring Boot Versions#1510

Open
seanmcilvenna wants to merge 2 commits intodevfrom
tech_debt/update-java-21
Open

TECH_DEBT: Updating Java and Spring Boot Versions#1510
seanmcilvenna wants to merge 2 commits intodevfrom
tech_debt/update-java-21

Conversation

@seanmcilvenna
Copy link
Copy Markdown
Contributor

@seanmcilvenna seanmcilvenna commented Mar 19, 2026

🛠️ Description of Changes

Upgrade to Java 21, migrate to jakarta.servlet, and align dependencies and configurations:

  • Updated base images to Java 21 in Dockerfiles.
  • Migrated javax.servlet imports to jakarta.servlet
  • Enabled Spring virtual threads for validation and measureeval.
  • Updated Spring Boot to 3.4.3 and aligned BOM dependencies (Spring Cloud, Azure, OpenTelemetry).
  • Upgraded HAPI-FHIR to version 7.6.1 with associated dependency updates.
  • Improved OpenTelemetry configuration following API changes.
  • Integrated CDS and optimized runtime entrypoints.

🧪 Testing Performed

Ran with docker compose, successfully tested against a basic synthetic test patient.

🧑‍🔬 Unit Testing

  • Coverage: 0.0%

Unit tests not applicable for this change.

📓 Documentation Updated

N/A

Summary by CodeRabbit

Release Notes

  • New Features

    • Enabled virtual threads support for improved concurrency handling.
  • Upgrades

    • Upgraded Java runtime from version 17 to 21 across all services.
    • Updated Spring Boot framework from 3.2.5 to 3.4.3.
    • Upgraded FHIR library to version 7.6.1 with latest healthcare standards support.
    • Updated OpenTelemetry observability framework and Spring Cloud integrations.
    • Enhanced API documentation support with latest Swagger/OpenAPI tools.
  • Performance

    • Optimized container startup times with Class Data Sharing (CDS) and improved jar extraction.

…ies and configurations:

- Updated base images to Java 21 in Dockerfiles.
- Migrated `javax.servlet` imports to `jakarta.servlet`
- Enabled Spring virtual threads for validation and measureeval.
- Updated Spring Boot to 3.4.3 and aligned BOM dependencies (Spring Cloud, Azure, OpenTelemetry).
- Upgraded HAPI-FHIR to version 7.6.1 with associated dependency updates.
- Improved OpenTelemetry configuration following API changes.
- Integrated CDS and optimized runtime entrypoints.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 19, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3a4316d3-e96c-4ed5-a183-be611129af3d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR upgrades the Java ecosystem across multiple modules: Spring Boot from 3.2.5 to 3.4.3, Java runtime from 17 to 21, and migrates servlet APIs from javax to jakarta. Docker configurations are refactored to use Spring Boot's jarmode extraction and JarLauncher with Class Data Sharing. OpenTelemetry and Spring Cloud dependencies are updated.

Changes

Cohort / File(s) Summary
Root Spring Boot & Framework Upgrade
Java/pom.xml
Updated parent Spring Boot 3.2.5→3.4.3, added Java 21 property, imported spring-framework-bom 6.2.3, spring-cloud-dependencies 2024.0.0, spring-cloud-azure-dependencies 5.19.0. Upgraded OpenTelemetry BOM 1.38.0→1.47.0, instrumentation 2.4.0-alpha→2.13.0-alpha, springdoc-openapi 2.5.0→2.8.5. Removed explicit Azure app-config-web and blob storage managed dependencies.
HAPI Module Upgrade
Java/hapi/pom.xml, Java/hapi/Dockerfile
Updated HAPI FHIR parent 5.6.0→7.6.1, switched servlet API javax→jakarta 6.0.0, upgraded Dockerfile to JDK 21, updated runtime image to hapi v7.6.1, added custom bean packages env var, adjusted launcher class path.
Jakarta Servlet Migration
Java/hapi/src/main/java/ca/uhn/fhir/.../filters/RequestConcurrencyLoggingFilter.java, Java/hapi/src/main/java/ca/uhn/fhir/.../filters/RequestRateLimitingFilter.java, Java/hapi/src/main/java/com/.../utils/HttpServletRequestUtils.java
Updated imports from javax.servlet.* to jakarta.servlet.* across filter chain and HTTP utility classes; no logic changes.
MeasureEval Module Update
Java/measureeval/pom.xml, Java/measureeval/Dockerfile, Java/measureeval/src/main/java/.../configs/OpenTelemetryConfig.java, Java/measureeval/src/main/resources/application.yml
Upgraded Dockerfile to Java 21, added jarmode extraction step and JarLauncher entrypoint with CDS support, removed explicit OpenTelemetry dependency versions. Updated service name attribute from constant to string key. Enabled virtual threads via spring.threads.virtual.enabled: true.
Validation Module Update
Java/validation/pom.xml, Java/validation/Dockerfile, Java/validation/src/main/java/.../configs/OpenTelemetryConfig.java, Java/validation/src/main/resources/application.yml
Upgraded Dockerfile to Java 21, added jarmode extraction and JarLauncher entrypoint with CDS. Removed explicit OpenTelemetry versions from pom.xml. Updated service name attribute key handling. Enabled virtual threads in application configuration.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

config-change

Suggested reviewers

  • smailliwcs
  • johnbritton
  • amphillipsLGC

Poem

🐰 From Java seventeen up to twenty-one we hop,
Spring Boot bounds ahead, frameworks never stop!
Jakarta calls where javax once did play,
Virtual threads and CDS light the way,
Telemetry sings in a brighter new day! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main objective of the PR: upgrading Java and Spring Boot versions along with related dependency updates.
Description check ✅ Passed The PR description covers all required template sections with sufficient detail about changes, testing performed, and notes on documentation and unit testing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tech_debt/update-java-21

Comment @coderabbitai help to get the list of available commands and usage tips.

@seanmcilvenna seanmcilvenna changed the title Upgrade to Java 21, migrate to jakarta.servlet, and align dependencies and configurations: - Updated base images to Java 21 in Dockerfiles. - Migrated javax.servlet imports to jakarta.servlet - Enabled Spring virtual threads for validation and measureeval. - Updated Spring Boot to 3.4.3 and aligned BOM dependencies (Spring Cloud, Azure, OpenTelemetry). - Upgraded HAPI-FHIR to version 7.6.1 with associated dependency updates. - Improved OpenTelemetry configuration following API changes. - Integrated CDS and optimized runtime entrypoints. TECH_DEBT: Updating Java and Spring Boot Versions Mar 19, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Java/measureeval/Dockerfile`:
- Around line 21-22: The Dockerfile currently runs the jarmode extraction (java
-Djarmode=tools -jar measureeval.jar extract --launcher ...) which only creates
the app layout but does not produce the CDS archive; add a training run
immediately after the extract to generate application.jsa by invoking the JVM
with -XX:ArchiveClassesAtExit=application.jsa and launching the Spring Boot
launcher (org.springframework.boot.loader.launch.JarLauncher) so the CDS archive
is created before the ENTRYPOINT attempts to load application.jsa.

In `@Java/pom.xml`:
- Around line 101-120: The RuntimeMetrics behavior changed in the updated
OpenTelemetry/instrumentation BOMs:
RuntimeMetrics.builder().enableAllFeatures().build() in your OpenTelemetryConfig
files now emits JVM runtime metrics in seconds (v2.8.0+); confirm downstream
consumers expect seconds and, if they expect milliseconds, either change the
consumers to interpret the unit as seconds or modify the metrics pipeline in
your OpenTelemetryConfig to convert values back to milliseconds and set the
metric unit to "ms" (or explicitly set the unit via the runtime metric
configuration API if available) so
RuntimeMetrics.builder().enableAllFeatures().build() produces values and unit
compatible with downstream systems.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6a1fc5b5-fb61-470a-a862-f9df4f76df1b

📥 Commits

Reviewing files that changed from the base of the PR and between c72d91a and 92747f7.

📒 Files selected for processing (14)
  • Java/hapi/Dockerfile
  • Java/hapi/pom.xml
  • Java/hapi/src/main/java/ca/uhn/fhir/jpa/starter/link/filters/RequestConcurrencyLoggingFilter.java
  • Java/hapi/src/main/java/ca/uhn/fhir/jpa/starter/link/filters/RequestRateLimitingFilter.java
  • Java/hapi/src/main/java/com/lantanagroup/link/hapi/utils/HttpServletRequestUtils.java
  • Java/measureeval/Dockerfile
  • Java/measureeval/pom.xml
  • Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/configs/OpenTelemetryConfig.java
  • Java/measureeval/src/main/resources/application.yml
  • Java/pom.xml
  • Java/validation/Dockerfile
  • Java/validation/pom.xml
  • Java/validation/src/main/java/com/lantanagroup/link/validation/configs/OpenTelemetryConfig.java
  • Java/validation/src/main/resources/application.yml

Comment on lines +21 to +22
# Extract using tools jarmode (replacement for layertools and cds in SB 3.4)
RUN java -Djarmode=tools -jar measureeval.jar extract --launcher --destination . --force
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Spring Boot 3.4 jarmode tools extract CDS archive generation

💡 Result:

To generate a CDS (AppCDS) archive for a Spring Boot 3.4 executable JAR, you use the tools jarmode to extract the JAR into a CDS-friendly layout, then do a “training run” that writes the archive on exit, and finally launch using that archive.

1) Extract the executable JAR (CDS-friendly layout)

java -Djarmode=tools -jar my-app.jar extract --destination application

This tools jarmode extract command is the modern replacement for -Djarmode=layertools extract. [1]

2) Training run: generate the CDS archive (application.jsa)

Run from the extracted directory (so the classpath/layout is stable for CDS):

cd application
java -XX:ArchiveClassesAtExit=application.jsa \
     -Dspring.context.exit=onRefresh \
     -jar my-app.jar

This produces application.jsa. [2][3]

3) Production run: use the archive

java -XX:SharedArchiveFile=application.jsa -jar my-app.jar

(Again, run using the same extracted layout.) [2][3]

Buildpacks (optional, container images)

If you build an image with Paketo buildpacks via Spring Boot, you can enable automatic CDS training + packaging by setting:

  • BP_JVM_CDS_ENABLED=true [4][5]

And if the training run needs different JVM/app options (e.g., to prevent early DB connections), you can use:

  • CDS_TRAINING_JAVA_TOOL_OPTIONS [5]

Sources: Spring Boot 3.3+ tools jarmode (still applies to 3.4) [1], Spring Boot CDS reference/how-to docs [2][4], Spring blog (CDS + self-extracting JAR workflow) [5].


Add missing CDS archive training run.

The extract --launcher command only creates the application structure; it does not generate the application.jsa CDS archive. A separate training run is required between extraction and production:

Required Docker step
RUN java -Djarmode=tools -jar measureeval.jar extract --launcher --destination . --force && \
    java -XX:ArchiveClassesAtExit=application.jsa \
         -Dspring.context.exit=onRefresh \
         org.springframework.boot.loader.launch.JarLauncher

Without this step, the ENTRYPOINT will fail when attempting to load the non-existent application.jsa archive.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Java/measureeval/Dockerfile` around lines 21 - 22, The Dockerfile currently
runs the jarmode extraction (java -Djarmode=tools -jar measureeval.jar extract
--launcher ...) which only creates the app layout but does not produce the CDS
archive; add a training run immediately after the extract to generate
application.jsa by invoking the JVM with
-XX:ArchiveClassesAtExit=application.jsa and launching the Spring Boot launcher
(org.springframework.boot.loader.launch.JarLauncher) so the CDS archive is
created before the ENTRYPOINT attempts to load application.jsa.

Comment thread Java/pom.xml
Comment on lines 101 to 120
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-bom</artifactId>
<version>1.38.0</version>
<version>1.47.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-bom-alpha</artifactId>
<version>2.4.0-alpha</version>
<version>2.13.0-alpha</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-runtime-telemetry-java17</artifactId>
<version>2.4.0-alpha</version>
<version>2.13.0-alpha</version>
</dependency>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify OpenTelemetry API changes that might affect code in the repository
# Search for OpenTelemetry usage patterns that may have changed between versions

echo "=== Searching for ResourceAttributes usage (deprecated in newer versions) ==="
rg -n "ResourceAttributes" --type java

echo "=== Searching for OpenTelemetry SDK configuration patterns ==="
rg -n "SdkTracerProvider|SdkMeterProvider|AutoConfiguredOpenTelemetrySdk" --type java

Repository: lantanagroup/link-cloud

Length of output: 1503


🏁 Script executed:

cat -n Java/validation/src/main/java/com/lantanagroup/link/validation/configs/OpenTelemetryConfig.java

Repository: lantanagroup/link-cloud

Length of output: 4410


🏁 Script executed:

cat -n Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/configs/OpenTelemetryConfig.java

Repository: lantanagroup/link-cloud

Length of output: 4028


🏁 Script executed:

# Check if there are any other OpenTelemetry related files
fd -e java -t f | xargs grep -l "opentelemetry" | head -20

Repository: lantanagroup/link-cloud

Length of output: 1550


🏁 Script executed:

grep -n "opentelemetry" Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/services/MeasureEvalMetrics.java

Repository: lantanagroup/link-cloud

Length of output: 309


🏁 Script executed:

grep -n "opentelemetry" Java/validation/src/main/java/com/lantanagroup/link/validation/services/MetricService.java

Repository: lantanagroup/link-cloud

Length of output: 219


🏁 Script executed:

grep -n "opentelemetry" Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/filters/TracingFilter.java

Repository: lantanagroup/link-cloud

Length of output: 108


🌐 Web query:

OpenTelemetry Java 1.38.0 to 1.47.0 breaking changes migration guide

💡 Result:

OpenTelemetry Java (opentelemetry-java) migration: 1.38.0 → 1.47.0 (breaking changes you must handle)

Use the BOM and upgrade all io.opentelemetry:* artifacts in lockstep to avoid runtime linkage issues (recommended for this repo).

1) 1.39.0 (2024-06-07) — Incubator + Prometheus exporter breaking changes

  • Incubator: ExtendedTracer / ExtendedSpanBuilder refactor (expect compilation fixes if you use the incubator APIs; update imports/usages to the new shapes). [1]
  • Prometheus exporter: Prometheus metrics are served only on /metrics by default (update your scrape config / reverse-proxy routing accordingly). [1]

2) 1.42.0 (2024-09-06) — Logs AnyValue body stabilization (type + package rename)

  • Breaking: Log body “AnyValue” stabilization:
    • AnyValue renamed to Value
    • moved from opentelemetry-api-incubator to opentelemetry-api
    • package changed from io.opentelemetry.api.incubator.logs to io.opentelemetry.api.common [2]
      Migration: update dependencies (stop relying on incubator for this) and fix imports/types to io.opentelemetry.api.common.Value.

3) 1.46.0 (2025-01-10) — Autoconfigure: deprecated property removed

  • Breaking: autoconfigure dropped support for deprecated otel.experimental.resource.disabled.keys.
    Migration: use otel.resource.disabled.keys instead. [3]

4) 1.47.0 (2025-02-07) — Event API/SDK removed

  • Breaking: Event API / SDK dropped. [4]
    Migration: replace Event API usage with the Logs API (and/or incubator helpers added around log “event name”, e.g. ExtendedLogRecordBuilder#setEventName, depending on what you were doing). [4]

Quick “what to do” checklist

  1. Update your BOM: io.opentelemetry:opentelemetry-bom:1.47.0 (and keep all OTel module versions aligned).
  2. If you use Prometheus exporter: ensure your Prometheus job scrapes /metrics. [1]
  3. If you emit structured log bodies: migrate AnyValueValue and fix imports/packages. [2]
  4. If you relied on Event API: migrate to Logs (Event API is gone in 1.47.0). [4]
  5. If you use autoconfigure resource-key disabling: replace otel.experimental.resource.disabled.keys with otel.resource.disabled.keys. [3]

Sources: OpenTelemetry Java CHANGELOG.md for 1.39.0, 1.42.0, 1.46.0, 1.47.0. [1][2][3][4]


🌐 Web query:

OpenTelemetry Instrumentation 2.4.0-alpha to 2.13.0-alpha breaking changes

💡 Result:

Breaking / migration-impacting changes called out in upstream opentelemetry-java-instrumentation release notes between v2.4.0 and v2.13.0 (these affect the 2.x.y-alpha artifacts shipped with each release):

  • v2.13.0 (2025-02-17)

    • io.opentelemetry.instrumentation.api.incubator.semconv.util.SpanNames deprecated → use stable io.opentelemetry.instrumentation.api.semconv.util.SpanNames.
    • Previously-deprecated HTTP library instrumentation classes/methods removed (from prior two releases).
    • Deprecated Dubbo instrumentation method removed. [1]
  • v2.12.0 (2025-01-17)

    • Some Java agent instrumentation suppression keys renamed to match module names (e.g., elasticsearch-rest-6.0elasticsearch-rest-6.4, internal-application-logginginternal-application-logger, etc.).
    • *TelemetryBuilder API parameter types changed: setCaptured*Headers(List)setCaptured*Headers(Collection); setKnownMethods(Set)setKnownMethods(Collection). [2]
  • v2.11.0 (2024-12-23) (prep for HTTP instrumentation stabilization)

    • addAttributeExtractor deprecated/renamedaddAttributesExtractor.
    • setEmitExperimental* deprecated and moved to internal/experimental classes.
    • ApacheHttpClient5* deprecated/renamedApacheHttpClient*.
    • RatpackTelemetry*, SpringWebfluxTelemetry*, ArmeriaTelemetry* deprecated/split into *ClientTelemetry* and *ServerTelemetry*.
    • *Ktor*Tracing* deprecated/renamed*Ktor*Telemetry*.
    • Experimental attribute spring-webflux.handler.type removed (use code.* attributes). [3]
  • v2.10.0 (2024-11-13)

    • Spring Boot Starter scheduling instrumentation scope name changed: io.opentelemetry.spring-scheduling-3.1io.opentelemetry.spring-boot-autoconfigure.
    • AWS Lambda javaagent default flush timeout changed 1s → 10s. [4]
  • v2.9.0 (2024-10-17)

    • Opt-in attribute renamed aws.requestIdaws.request_id, and it is now emitted by default. [5]
  • v2.8.0 (2024-09-13)

    • Java 17 JFR-based runtime metrics unit changed milliseconds → seconds (only if opted-in via otel.instrumentation.runtime-telemetry-java17.enable-all=true). [6]
  • v2.6.0 (2024-07-17)

    • Spring Boot autoconfigure artifacts merged: opentelemetry-spring-boot + opentelemetry-spring-boot-3opentelemetry-spring-boot-autoconfigure (supports both Boot 2 & 3).
    • Experimental HTTP metrics renamed: http.server.request.sizehttp.server.request.body.size; http.server.response.sizehttp.server.response.body.size. [7]
  • v2.4.0 (2024-05-17)

    • Removed incubating semconv dependency from library instrumentation (can affect dependency graphs). [8]

Sources: GitHub release notes for v2.13.0 [1], v2.12.0 [2], v2.11.0 [3], v2.10.0 [4], v2.9.0 [5], v2.8.0 [6], v2.6.0 [7], v2.4.0 [8].


Verify RuntimeMetrics metric unit compatibility with downstream systems.

The OpenTelemetry BOM update (1.38.0 → 1.47.0) and instrumentation update (2.4.0-alpha → 2.13.0-alpha) introduce significant changes. Most breaking changes in these ranges do not affect the code here (e.g., Event API removal, AnyValue renaming, Prometheus exporter config). However, v2.8.0 introduced a breaking change directly affecting this codebase: Java 17 runtime metrics now emit in seconds instead of milliseconds when using RuntimeMetrics.builder().enableAllFeatures().build() (lines 71 in both OpenTelemetryConfig files).

Confirm that downstream systems consuming these metrics expect second-based units, or update the metric unit configuration accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Java/pom.xml` around lines 101 - 120, The RuntimeMetrics behavior changed in
the updated OpenTelemetry/instrumentation BOMs:
RuntimeMetrics.builder().enableAllFeatures().build() in your OpenTelemetryConfig
files now emits JVM runtime metrics in seconds (v2.8.0+); confirm downstream
consumers expect seconds and, if they expect milliseconds, either change the
consumers to interpret the unit as seconds or modify the metrics pipeline in
your OpenTelemetryConfig to convert values back to milliseconds and set the
metric unit to "ms" (or explicitly set the unit via the runtime metric
configuration API if available) so
RuntimeMetrics.builder().enableAllFeatures().build() produces values and unit
compatible with downstream systems.

Copy link
Copy Markdown
Contributor

@smailliwcs smailliwcs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hapi module needs to be compatible with the version of HAPI running on ehr-test. (See notes in the POM and Dockerfile.) So until we're ready to upgrade ehr-test, we need to keep the module on Java 11 and HAPI 5.6.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants