Skip to content

Commit d2783fe

Browse files
author
brettheap
committed
chore: upgrade Java development and analysis toolchain
1 parent 22b6869 commit d2783fe

5 files changed

Lines changed: 130 additions & 64 deletions

File tree

Dockerfile.layer2

Lines changed: 121 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
1+
# syntax=docker/dockerfile:1.7
12
# Layer 2: Java Bench Image
23
# Extends Layer 1 (dev-bench-base) with Java specific tools
3-
# Includes: OpenJDK 21, Maven, Gradle, Spring CLI, Java tools
4+
# Includes: OpenJDK 25, Maven, Gradle, Spring CLI, Java tools
45

56
ARG BASE_IMAGE=dev-bench-base:latest
67
FROM ${BASE_IMAGE}
78

8-
ARG JACOCO_VERSION=0.8.14
9-
ARG SONAR_MAVEN_PLUGIN_VERSION=5.6.0.6792
10-
ARG SONAR_GRADLE_PLUGIN_VERSION=7.3.0.8198
9+
ARG JACOCO_VERSION=0.8.15
10+
ARG SONAR_MAVEN_PLUGIN_VERSION=5.7.0.6970
11+
ARG SONAR_GRADLE_PLUGIN_VERSION=7.3.1.8318
12+
ARG SPRINGBOOT_VERSION=4.1.0
13+
ARG KOTLIN_VERSION=2.4.0
14+
ARG GROOVY_VERSION=6.0.0-alpha-1
15+
ARG SCALA_VERSION=3.8.4
16+
ARG QUARKUS_VERSION=3.37.1
17+
ARG MICRONAUT_VERSION=5.0.3
1118

1219
# Container version labels
1320
LABEL layer="2"
1421
LABEL layer.name="java-bench"
15-
LABEL layer.version="2.2.1"
22+
LABEL layer.version="2.2.10"
1623
LABEL layer.description="Java development tools and JDK (user-agnostic)"
1724
LABEL bench.type="java"
1825

@@ -23,21 +30,27 @@ USER root
2330
# JAVA JDK
2431
# ========================================
2532

26-
# Install OpenJDK 21 (LTS) and zip (required by SDKMAN)
33+
# Install OpenJDK 25 (LTS) and zip (required by SDKMAN)
2734
RUN apt-get update && apt-get install -y \
28-
openjdk-21-jdk \
29-
openjdk-21-jre \
35+
openjdk-25-jdk \
36+
openjdk-25-jre \
3037
zip \
3138
&& rm -rf /var/lib/apt/lists/*
3239

3340
# Set JAVA_HOME
34-
ENV JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
41+
ENV JAVA_HOME=/usr/lib/jvm/java-25-openjdk-amd64
3542
ENV PATH="$JAVA_HOME/bin:$PATH"
3643
ENV JACOCO_VERSION=${JACOCO_VERSION}
3744
ENV JACOCO_CLI_JAR=/opt/jacoco/jacococli.jar
3845
ENV JACOCO_AGENT_JAR=/opt/jacoco/jacocoagent.jar
3946
ENV SONAR_MAVEN_PLUGIN_VERSION=${SONAR_MAVEN_PLUGIN_VERSION}
4047
ENV SONAR_GRADLE_PLUGIN_VERSION=${SONAR_GRADLE_PLUGIN_VERSION}
48+
ENV SPRINGBOOT_VERSION=${SPRINGBOOT_VERSION}
49+
ENV KOTLIN_VERSION=${KOTLIN_VERSION}
50+
ENV GROOVY_VERSION=${GROOVY_VERSION}
51+
ENV SCALA_VERSION=${SCALA_VERSION}
52+
ENV QUARKUS_VERSION=${QUARKUS_VERSION}
53+
ENV MICRONAUT_VERSION=${MICRONAUT_VERSION}
4154

4255
# ========================================
4356
# BUILD TOOLS
@@ -48,29 +61,83 @@ RUN apt-get update && apt-get install -y \
4861
maven \
4962
&& rm -rf /var/lib/apt/lists/*
5063

51-
# Install Gradle
52-
RUN wget https://services.gradle.org/distributions/gradle-8.5-bin.zip -P /tmp \
53-
&& unzip -d /opt/gradle /tmp/gradle-8.5-bin.zip \
54-
&& rm /tmp/gradle-8.5-bin.zip \
55-
&& ln -s /opt/gradle/gradle-8.5/bin/gradle /usr/local/bin/gradle
64+
# Install a resumable, multi-connection downloader for large tool distributions.
65+
RUN apt-get update && apt-get install -y --no-install-recommends \
66+
aria2 \
67+
&& rm -rf /var/lib/apt/lists/*
5668

57-
ENV GRADLE_HOME=/opt/gradle/gradle-8.5
69+
# Install Gradle
70+
RUN aria2c --allow-overwrite=true --auto-file-renaming=false --continue=true \
71+
--max-connection-per-server=8 --split=8 --min-split-size=8M \
72+
--max-tries=10 --retry-wait=5 --connect-timeout=30 --timeout=120 \
73+
--summary-interval=30 --console-log-level=warn \
74+
-d /tmp -o gradle-9.6.1-bin.zip \
75+
https://downloads.gradle.org/distributions/gradle-9.6.1-bin.zip \
76+
&& unzip -d /opt/gradle /tmp/gradle-9.6.1-bin.zip \
77+
&& rm /tmp/gradle-9.6.1-bin.zip \
78+
&& ln -s /opt/gradle/gradle-9.6.1/bin/gradle /usr/local/bin/gradle
79+
80+
ENV GRADLE_HOME=/opt/gradle/gradle-9.6.1
5881
ENV PATH="$GRADLE_HOME/bin:$PATH"
82+
ENV CURL_RETRY_OPTS="--retry 5 --retry-delay 5 --connect-timeout 30 --max-time 600"
83+
ENV ARIA2_RETRY_OPTS="--allow-overwrite=true --auto-file-renaming=false --continue=true --max-connection-per-server=8 --split=8 --min-split-size=1M --max-tries=10 --retry-wait=5 --connect-timeout=30 --timeout=120 --summary-interval=30 --console-log-level=warn"
5984

6085
# ========================================
6186
# JAVA DEVELOPMENT TOOLS
6287
# ========================================
6388

6489
# Install SDKMan system-wide
65-
RUN curl -s "https://get.sdkman.io" | bash \
90+
RUN curl -fsSL $CURL_RETRY_OPTS "https://get.sdkman.io" | bash \
6691
&& bash -c "source $HOME/.sdkman/bin/sdkman-init.sh" \
6792
&& mv /root/.sdkman /opt/sdkman \
6893
&& chmod -R a+rx /opt/sdkman
6994

7095
ENV SDKMAN_DIR=/opt/sdkman
7196

72-
# Install Spring Boot CLI via SDKMAN
73-
RUN bash -c "source /opt/sdkman/bin/sdkman-init.sh && sdk install springboot" || true
97+
# Install SDKMAN-hosted JVM CLIs with resumable downloads. Calling `sdk install`
98+
# directly can fall back to a slow single-stream downloader inside Docker.
99+
RUN --mount=type=cache,target=/var/cache/sdkman-downloads,sharing=locked set -eux; \
100+
install_sdkman_archive() { \
101+
candidate="$1"; \
102+
version="$2"; \
103+
install_dir="/opt/${candidate}-${version}"; \
104+
download_dir="/var/cache/sdkman-downloads"; \
105+
archive="${download_dir}/${candidate}-${version}.zip"; \
106+
extract_dir="/tmp/${candidate}-${version}-extract"; \
107+
rm -rf "$install_dir" "$extract_dir"; \
108+
mkdir -p "$download_dir" "$extract_dir"; \
109+
aria2c $ARIA2_RETRY_OPTS -d "$download_dir" -o "$(basename "$archive")" \
110+
"https://api.sdkman.io/2/broker/download/${candidate}/${version}/linuxx64"; \
111+
unzip -q "$archive" -d "$extract_dir"; \
112+
first_entry="$(find "$extract_dir" -mindepth 1 -maxdepth 1 | head -n1)"; \
113+
entry_count="$(find "$extract_dir" -mindepth 1 -maxdepth 1 | wc -l)"; \
114+
mkdir -p "$install_dir"; \
115+
if [ "$entry_count" = "1" ] && [ -d "$first_entry" ]; then \
116+
cp -a "$first_entry"/. "$install_dir"/; \
117+
else \
118+
cp -a "$extract_dir"/. "$install_dir"/; \
119+
fi; \
120+
chmod -R a+rX "$install_dir"; \
121+
if [ -d "$install_dir/bin" ]; then \
122+
find "$install_dir/bin" -maxdepth 1 -type f -perm /111 -printf '%f\n' | \
123+
while read -r tool; do \
124+
ln -sf "$install_dir/bin/$tool" "/usr/local/bin/$tool"; \
125+
done; \
126+
fi; \
127+
rm -rf "$extract_dir"; \
128+
}; \
129+
install_sdkman_archive springboot "$SPRINGBOOT_VERSION"; \
130+
install_sdkman_archive kotlin "$KOTLIN_VERSION"; \
131+
install_sdkman_archive groovy "$GROOVY_VERSION"; \
132+
install_sdkman_archive scala "$SCALA_VERSION"; \
133+
install_sdkman_archive quarkus "$QUARKUS_VERSION"; \
134+
install_sdkman_archive micronaut "$MICRONAUT_VERSION"; \
135+
spring --version; \
136+
kotlin -version; \
137+
groovy --version; \
138+
scala -version; \
139+
quarkus --version; \
140+
mn --version
74141

75142
# ========================================
76143
# CONTAINER TOOLING
@@ -80,7 +147,7 @@ RUN bash -c "source /opt/sdkman/bin/sdkman-init.sh && sdk install springboot" ||
80147
RUN apt-get update && apt-get install -y \
81148
ca-certificates gnupg lsb-release \
82149
&& install -m 0755 -d /etc/apt/keyrings \
83-
&& curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --batch --yes --dearmor -o /etc/apt/keyrings/docker.gpg \
150+
&& curl -fsSL $CURL_RETRY_OPTS https://download.docker.com/linux/ubuntu/gpg | gpg --batch --yes --dearmor -o /etc/apt/keyrings/docker.gpg \
84151
&& chmod a+r /etc/apt/keyrings/docker.gpg \
85152
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list \
86153
&& apt-get update && apt-get install -y docker-ce-cli docker-compose-plugin \
@@ -103,33 +170,37 @@ RUN apt-get update && apt-get install -y \
103170

104171
# Checkstyle
105172
RUN mkdir -p /opt/checkstyle \
106-
&& curl -fsSL https://github.com/checkstyle/checkstyle/releases/download/checkstyle-10.21.4/checkstyle-10.21.4-all.jar \
107-
-o /opt/checkstyle/checkstyle.jar
173+
&& aria2c $ARIA2_RETRY_OPTS -d /opt/checkstyle -o checkstyle.jar \
174+
https://github.com/checkstyle/checkstyle/releases/download/checkstyle-13.7.0/checkstyle-13.7.0-all.jar \
175+
&& printf '%s\n' '#!/usr/bin/env sh' \
176+
'exec java -jar /opt/checkstyle/checkstyle.jar "$@"' \
177+
> /usr/local/bin/checkstyle \
178+
&& chmod +x /usr/local/bin/checkstyle
108179

109180
# SpotBugs
110-
RUN curl -fsSL https://github.com/spotbugs/spotbugs/releases/download/4.8.6/spotbugs-4.8.6.tgz \
111-
-o /tmp/spotbugs.tgz \
181+
RUN aria2c $ARIA2_RETRY_OPTS -d /tmp -o spotbugs.tgz \
182+
https://github.com/spotbugs/spotbugs/releases/download/4.10.2/spotbugs-4.10.2.tgz \
112183
&& tar -xzf /tmp/spotbugs.tgz -C /opt \
113-
&& chmod +x /opt/spotbugs-4.8.6/bin/spotbugs \
114-
&& ln -s /opt/spotbugs-4.8.6/bin/spotbugs /usr/local/bin/spotbugs \
184+
&& chmod +x /opt/spotbugs-4.10.2/bin/spotbugs \
185+
&& ln -s /opt/spotbugs-4.10.2/bin/spotbugs /usr/local/bin/spotbugs \
115186
&& rm /tmp/spotbugs.tgz
116187

117188
# PMD
118-
RUN curl -fsSL https://github.com/pmd/pmd/releases/download/pmd_releases%2F7.9.0/pmd-dist-7.9.0-bin.zip \
119-
-o /tmp/pmd.zip \
189+
RUN aria2c $ARIA2_RETRY_OPTS -d /tmp -o pmd.zip \
190+
https://github.com/pmd/pmd/releases/download/pmd_releases%2F7.26.0/pmd-dist-7.26.0-bin.zip \
120191
&& unzip -q /tmp/pmd.zip -d /opt \
121-
&& ln -s /opt/pmd-bin-7.9.0/bin/pmd /usr/local/bin/pmd \
192+
&& ln -s /opt/pmd-bin-7.26.0/bin/pmd /usr/local/bin/pmd \
122193
&& rm /tmp/pmd.zip
123194

124195
# JaCoCo coverage tools for manual Java coverage workflows.
125196
COPY files/jacoco-cli /usr/local/bin/jacoco-cli
126197
COPY files/sonarcloud-java-maven /usr/local/bin/sonarcloud-java-maven
127198
COPY files/sonarcloud-java-gradle /usr/local/bin/sonarcloud-java-gradle
128199
RUN mkdir -p /opt/jacoco \
129-
&& curl -fsSL "https://repo1.maven.org/maven2/org/jacoco/org.jacoco.cli/${JACOCO_VERSION}/org.jacoco.cli-${JACOCO_VERSION}-nodeps.jar" \
130-
-o /opt/jacoco/jacococli.jar \
131-
&& curl -fsSL "https://repo1.maven.org/maven2/org/jacoco/org.jacoco.agent/${JACOCO_VERSION}/org.jacoco.agent-${JACOCO_VERSION}-runtime.jar" \
132-
-o /opt/jacoco/jacocoagent.jar \
200+
&& aria2c $ARIA2_RETRY_OPTS -d /opt/jacoco -o jacococli.jar \
201+
"https://repo1.maven.org/maven2/org/jacoco/org.jacoco.cli/${JACOCO_VERSION}/org.jacoco.cli-${JACOCO_VERSION}-nodeps.jar" \
202+
&& aria2c $ARIA2_RETRY_OPTS -d /opt/jacoco -o jacocoagent.jar \
203+
"https://repo1.maven.org/maven2/org/jacoco/org.jacoco.agent/${JACOCO_VERSION}/org.jacoco.agent-${JACOCO_VERSION}-runtime.jar" \
133204
&& chmod +x /usr/local/bin/jacoco-cli /usr/local/bin/sonarcloud-java-maven /usr/local/bin/sonarcloud-java-gradle
134205

135206
# SonarScanner CLI is inherited from dev-bench-base.
@@ -139,12 +210,6 @@ RUN sonar-scanner --version
139210
# JVM COMPANION LANGUAGES
140211
# ========================================
141212

142-
# Install Kotlin, Groovy, Scala via SDKMAN
143-
RUN bash -c "source /opt/sdkman/bin/sdkman-init.sh \
144-
&& sdk install kotlin \
145-
&& sdk install groovy \
146-
&& sdk install scala" || true
147-
148213
# ========================================
149214
# SYSTEM UTILITIES
150215
# ========================================
@@ -160,35 +225,36 @@ RUN apt-get update && apt-get install -y \
160225
# FRAMEWORK CLIs
161226
# ========================================
162227

163-
# Install Quarkus and Micronaut CLIs via SDKMAN
164-
# (Spring Boot CLI already installed above)
165-
RUN bash -c "source /opt/sdkman/bin/sdkman-init.sh \
166-
&& sdk install quarkus \
167-
&& sdk install micronaut" || true
168-
169228
# ========================================
170229
# PERFORMANCE TESTING
171230
# ========================================
172231

173232
# Apache JMeter 5.6.3
174-
RUN curl -fsSL https://dlcdn.apache.org/jmeter/binaries/apache-jmeter-5.6.3.tgz \
175-
-o /tmp/jmeter.tgz \
233+
RUN aria2c $ARIA2_RETRY_OPTS -d /tmp -o jmeter.tgz \
234+
https://dlcdn.apache.org/jmeter/binaries/apache-jmeter-5.6.3.tgz \
176235
&& tar -xzf /tmp/jmeter.tgz -C /opt \
177236
&& ln -s /opt/apache-jmeter-5.6.3/bin/jmeter /usr/local/bin/jmeter \
178237
&& rm /tmp/jmeter.tgz
179238

180239
ENV JMETER_HOME=/opt/apache-jmeter-5.6.3
181240

182-
# Gatling 3.10.5 (last traditional standalone bundle)
183-
RUN curl -fsSL https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/3.10.5/gatling-charts-highcharts-bundle-3.10.5-bundle.zip \
184-
-o /tmp/gatling.zip \
241+
# Gatling standalone bundle
242+
RUN aria2c $ARIA2_RETRY_OPTS -d /tmp -o gatling.zip \
243+
https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/3.15.1/gatling-charts-highcharts-bundle-3.15.1-bundle.zip \
185244
&& unzip -q /tmp/gatling.zip -d /opt \
186-
&& chmod +x /opt/gatling-charts-highcharts-bundle-3.10.5/bin/*.sh \
187-
&& ln -s /opt/gatling-charts-highcharts-bundle-3.10.5/bin/gatling.sh /usr/local/bin/gatling \
188-
&& ln -s /opt/gatling-charts-highcharts-bundle-3.10.5/bin/recorder.sh /usr/local/bin/gatling-recorder \
245+
&& chmod +x /opt/gatling-charts-highcharts-bundle-3.15.1/mvnw \
246+
&& printf '%s\n' '#!/usr/bin/env bash' \
247+
'set -euo pipefail' \
248+
'cd "${GATLING_HOME:-/opt/gatling-charts-highcharts-bundle-3.15.1}"' \
249+
'exec ./mvnw gatling:test "$@"' > /usr/local/bin/gatling \
250+
&& printf '%s\n' '#!/usr/bin/env bash' \
251+
'set -euo pipefail' \
252+
'cd "${GATLING_HOME:-/opt/gatling-charts-highcharts-bundle-3.15.1}"' \
253+
'exec ./mvnw gatling:recorder "$@"' > /usr/local/bin/gatling-recorder \
254+
&& chmod +x /usr/local/bin/gatling /usr/local/bin/gatling-recorder \
189255
&& rm /tmp/gatling.zip
190256

191-
ENV GATLING_HOME=/opt/gatling-charts-highcharts-bundle-3.10.5
257+
ENV GATLING_HOME=/opt/gatling-charts-highcharts-bundle-3.15.1
192258

193259
# ========================================
194260
# WORKSPACE SETUP
@@ -213,8 +279,8 @@ RUN mkdir -p /etc/skel/.m2 \
213279
# Add Java development aliases and environment to /etc/skel/.zshrc
214280
RUN echo '' >> /etc/skel/.zshrc && \
215281
echo '# Java Development Environment' >> /etc/skel/.zshrc && \
216-
echo 'export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64' >> /etc/skel/.zshrc && \
217-
echo 'export GRADLE_HOME=/opt/gradle/gradle-8.5' >> /etc/skel/.zshrc && \
282+
echo 'export JAVA_HOME=/usr/lib/jvm/java-25-openjdk-amd64' >> /etc/skel/.zshrc && \
283+
echo 'export GRADLE_HOME=/opt/gradle/gradle-9.6.1' >> /etc/skel/.zshrc && \
218284
echo 'export PATH="$JAVA_HOME/bin:$GRADLE_HOME/bin:$PATH"' >> /etc/skel/.zshrc && \
219285
echo '' >> /etc/skel/.zshrc && \
220286
echo '# Java Build aliases' >> /etc/skel/.zshrc && \

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ A comprehensive Java development environment using layered Docker containers.
55
## Features
66

77
### Java Development Stack
8-
- **OpenJDK 21** (LTS version)
8+
- **OpenJDK 25** (LTS version)
99
- **Maven** - Dependency management and build tool
1010
- **Gradle 8.5** - Modern build automation
1111
- **Spring Boot CLI** - Spring application scaffolding
@@ -112,7 +112,7 @@ For Gradle projects, the project must apply both plugins:
112112
```groovy
113113
plugins {
114114
id "jacoco"
115-
id "org.sonarqube" version "7.3.0.8198"
115+
id "org.sonarqube" version "7.3.1.8318"
116116
}
117117
118118
jacocoTestReport {
@@ -171,8 +171,8 @@ sdk use java 17.0.9-tem
171171
Maven is configured to use `/workspace/m2repo` for the local repository, persisting dependencies across container restarts.
172172

173173
### Environment Variables
174-
- `JAVA_HOME`: `/usr/lib/jvm/java-21-openjdk-amd64`
175-
- `GRADLE_HOME`: `/opt/gradle/gradle-8.5`
174+
- `JAVA_HOME`: `/usr/lib/jvm/java-25-openjdk-amd64`
175+
- `GRADLE_HOME`: `/opt/gradle/gradle-9.6.1`
176176
- `MAVEN_OPTS`: `-Dmaven.repo.local=/workspace/m2repo`
177177

178178
## Useful Aliases
@@ -221,7 +221,7 @@ echo $JAVA_HOME
221221

222222
- Container Version: 1.0.0
223223
- Base Image: dev-bench-base
224-
- JDK Version: OpenJDK 21
224+
- JDK Version: OpenJDK 25
225225
- Maven Version: (from apt)
226226
- Gradle Version: 8.5
227227
- Spring Boot CLI: 3.2.0

files/sonarcloud-java-gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ if [[ -z "${SONAR_HOST_URL:-}" && -n "${SONARQUBE_URL:-}" ]]; then
2323
fi
2424

2525
export SONAR_HOST_URL="${SONAR_HOST_URL:-https://sonarcloud.io}"
26-
export SONAR_GRADLE_PLUGIN_VERSION="${SONAR_GRADLE_PLUGIN_VERSION:-7.3.0.8198}"
26+
export SONAR_GRADLE_PLUGIN_VERSION="${SONAR_GRADLE_PLUGIN_VERSION:-7.3.1.8318}"
2727

2828
if [[ -z "${SONAR_TOKEN:-}" ]]; then
2929
echo "Missing SONAR_TOKEN or SONARQUBE_TOKEN." >&2

files/sonarcloud-java-maven

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ if [[ -z "${SONAR_HOST_URL:-}" && -n "${SONARQUBE_URL:-}" ]]; then
2222
fi
2323

2424
export SONAR_HOST_URL="${SONAR_HOST_URL:-https://sonarcloud.io}"
25-
export JACOCO_VERSION="${JACOCO_VERSION:-0.8.14}"
26-
export SONAR_MAVEN_PLUGIN_VERSION="${SONAR_MAVEN_PLUGIN_VERSION:-5.6.0.6792}"
25+
export JACOCO_VERSION="${JACOCO_VERSION:-0.8.15}"
26+
export SONAR_MAVEN_PLUGIN_VERSION="${SONAR_MAVEN_PLUGIN_VERSION:-5.7.0.6970}"
2727

2828
if [[ -z "${SONAR_TOKEN:-}" ]]; then
2929
echo "Missing SONAR_TOKEN or SONARQUBE_TOKEN." >&2

setup.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ if docker-compose -f "$SCRIPT_DIR/.devcontainer/docker-compose.yml" up -d; then
4545
echo " docker ps | grep java_bench"
4646
echo ""
4747
echo "☕ Java Development Ready:"
48-
echo " - OpenJDK 21 (LTS)"
48+
echo " - OpenJDK 25 (LTS)"
4949
echo " - Maven and Gradle build tools"
5050
echo " - Spring Boot CLI"
5151
echo " - SDKMan for version management"

0 commit comments

Comments
 (0)