diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index ee0b5c7..bf9aea2 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -41,6 +41,7 @@ jobs:
- '25'
app:
- springboot3
+ - springboot4
- quarkus3
- quarkus3-spring-compatibility
name: "[jvm-build-test-java${{ matrix.java }}]: ${{ matrix.app }}"
@@ -70,6 +71,9 @@ jobs:
springboot3)
TARGET_DIR="${{ matrix.app }}/target/springboot3.jar"
;;
+ springboot4)
+ TARGET_DIR="${{ matrix.app }}/target/springboot4.jar"
+ ;;
quarkus3)
TARGET_DIR="${{ matrix.app }}/target/quarkus-app/quarkus-run.jar"
;;
diff --git a/pom.xml b/pom.xml
index 0a192d1..32dd7c4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,5 +12,6 @@
quarkus3
quarkus3-spring-compatibility
springboot3
+ springboot4
-
\ No newline at end of file
+
diff --git a/scripts/perf-lab/main.yml b/scripts/perf-lab/main.yml
index 8f5b0c9..5a3af8c 100644
--- a/scripts/perf-lab/main.yml
+++ b/scripts/perf-lab/main.yml
@@ -8,13 +8,15 @@ states:
config.jvm.graalvm.version: 25.0.1-graalce
config.quarkus.version: #3.28.3
- config.springboot.version: #3.5.6
+ config.springboot3.version: #3.5.6
+ config.springboot4.version: #4.0.0
config.jvm.memory: #-Xmx128m
config.jvm.args: #-XX:+UseNUMA
config.quarkus.native_build_options: #-Dquarkus.native.native-image-xmx=
- config.springboot.native_build_options:
+ config.springboot3.native_build_options:
+ config.springboot4.native_build_options:
config.resources.cpu.app: 0-3
config.resources.cpu.db: 4-6
@@ -39,7 +41,7 @@ states:
PROFILER_JVM_ARGS:
BASE_JAVA_CMD: ${{APP_CMD_PREFIX}} java ${{config.jvm.memory}} ${{config.jvm.args}} ${{PROFILER_JVM_ARGS}}
TESTS : [test-build, measure-build-times, measure-time-to-first-request, measure-rss, run-load-test]
- RUNTIMES: [quarkus3-jvm, quarkus3-native, spring3-jvm, spring3-jvm-aot, spring3-native]
+ RUNTIMES: [quarkus3-jvm, quarkus3-native, spring3-jvm, spring3-jvm-aot, spring3-native, spring4-jvm, spring4-jvm-aot, spring4-native]
TARGET_URL: http://localhost:8080/fruits
QUARKUS-PLATFORM-ARTIFACT-ID: quarkus-bom
PROJ_REPO_NAME: spring-quarkus-perf-comparison
@@ -64,23 +66,44 @@ states:
type: jvm
dir: ${{SPRING3_BOOT_DIR}}
updateScript: update-spring-boot-version
- updateVersion: ${{config.springboot.version}}
+ updateVersion: ${{config.springboot3.version}}
buildCmd: "./mvnw clean package -DskipTests"
runCmd: "${{BASE_JAVA_CMD}} -jar ${{SPRING3_BOOT_DIR}}/target/springboot3.jar"
- name: spring3-jvm-aot
type: jvm
dir: ${{SPRING3_BOOT_DIR}}
updateScript: update-spring-boot-version
- updateVersion: ${{config.springboot.version}}
+ updateVersion: ${{config.springboot3.version}}
buildCmd: "./mvnw clean compile spring-boot:process-aot package -DskipTests"
runCmd: "${{BASE_JAVA_CMD}} -Dspring.aot.enabled=true -jar ${{SPRING3_BOOT_DIR}}/target/springboot3.jar"
- name: spring3-native
type: native
dir: ${{SPRING3_BOOT_DIR}}
updateScript: update-spring-boot-version
- updateVersion: ${{config.springboot.version}}
- buildCmd: "./mvnw clean -Pnative -DskipTests native:compile package ${{config.springboot.native_build_options}}"
+ updateVersion: ${{config.springboot3.version}}
+ buildCmd: "./mvnw clean -Pnative -DskipTests native:compile package ${{config.springboot3.native_build_options}}"
runCmd: "${{APP_CMD_PREFIX}} ${{SPRING3_BOOT_DIR}}/target/springboot3 ${{config.jvm.memory}}"
+ - name: spring4-jvm
+ type: jvm
+ dir: ${{SPRING4_BOOT_DIR}}
+ updateScript: update-spring-boot-version
+ updateVersion: ${{config.springboot4.version}}
+ buildCmd: "./mvnw clean package -DskipTests"
+ runCmd: "${{BASE_JAVA_CMD}} -jar ${{SPRING4_BOOT_DIR}}/target/springboot4.jar"
+ - name: spring4-jvm-aot
+ type: jvm
+ dir: ${{SPRING4_BOOT_DIR}}
+ updateScript: update-spring-boot-version
+ updateVersion: ${{config.springboot4.version}}
+ buildCmd: "./mvnw clean compile spring-boot:process-aot package -DskipTests"
+ runCmd: "${{BASE_JAVA_CMD}} -Dspring.aot.enabled=true -jar ${{SPRING4_BOOT_DIR}}/target/springboot4.jar"
+ - name: spring4-native
+ type: native
+ dir: ${{SPRING4_BOOT_DIR}}
+ updateScript: update-spring-boot-version
+ updateVersion: ${{config.springboot4.version}}
+ buildCmd: "./mvnw clean -Pnative -DskipTests native:compile package ${{config.springboot4.native_build_options}}"
+ runCmd: "${{APP_CMD_PREFIX}} ${{SPRING4_BOOT_DIR}}/target/springboot4 ${{config.jvm.memory}}"
scripts:
update-state:
@@ -90,8 +113,9 @@ scripts:
- set-state: RUN.PROJ_REPO_DIR ${{REPO_DIR}}/${{PROJ_REPO_NAME}}
- set-state: RUN.SCRIPTS_DIR ${{PROJ_REPO_DIR}}/scripts/perf-lab
- set-state: RUN.HELPER_SCRIPTS_DIR ${{SCRIPTS_DIR}}/scripts
- - set-state: RUN.SPRING3_BOOT_DIR ${{PROJ_REPO_DIR}}/springboot3
- - set-state: RUN.QUARKUS3_DIR ${{PROJ_REPO_DIR}}/quarkus3
+ - set-state: RUN.SPRING3_BOOT_DIR ${{REPO_DIR}}/${{PROJ_REPO_NAME}}/springboot3
+ - set-state: RUN.SPRING4_BOOT_DIR ${{REPO_DIR}}/${{PROJ_REPO_NAME}}/springboot4
+ - set-state: RUN.QUARKUS3_DIR ${{REPO_DIR}}/${{PROJ_REPO_NAME}}/quarkus3
- set-state: RUN.ASYNC_PROFILER_DIR ${{BASE_DIR}}/${{ASYNC_PROFILER}}
output-vars:
@@ -102,6 +126,7 @@ scripts:
"REPO_DIR: ${{REPO_DIR}}"
"SCRIPTS_DIR: ${{SCRIPTS_DIR}}"
"SPRING3_BOOT_DIR: ${{SPRING3_BOOT_DIR}}"
+ "SPRING4_BOOT_DIR: ${{SPRING4_BOOT_DIR}}"
"QUARKUS3_DIR : ${{QUARKUS3_DIR}}"
"RUNTIMECMDS: ${{RUNTIMECMDS}}"
diff --git a/scripts/perf-lab/run-benchmarks.sh b/scripts/perf-lab/run-benchmarks.sh
index 3b708f1..058cb04 100755
--- a/scripts/perf-lab/run-benchmarks.sh
+++ b/scripts/perf-lab/run-benchmarks.sh
@@ -9,48 +9,52 @@ help() {
echo
echo "Syntax: run-benchmarks.sh [options]"
echo "options:"
- echo " -a Any JVM args to be passed to the apps"
- echo " -b The branch in the SCM repo"
- echo " Default: '${SCM_REPO_BRANCH}'"
- echo " -c How many CPUs to allocate to the application"
- echo " Default: ${CPUS}"
- echo " -d Purge/drop OS filesystem caches between iterations"
- echo " -e Any extra arguments that need to be passed to qDup ahead of the qDup scripts"
- echo " NOTE: This is an advanced option. Make sure you know what you are doing when using it."
- echo " -f The directory containing the run output"
- echo " Default: ${OUTPUT_DIR}"
- echo " -g The GraalVM version to use if running any native tests (from SDKMAN)"
- echo " Default: ${GRAALVM_VERSION}"
- echo " -h The HOST to run the benchmarks on"
- echo " LOCAL is a keyword that can be used to run everything on the local machine"
- echo " Default: ${HOST}"
- echo " -i The number of iterations to run each test"
- echo " Default: ${ITERATIONS}"
- echo " -j The Java version to use (from SDKMAN)"
- echo " Default: ${JAVA_VERSION}"
- echo " -l The SCM repo url"
- echo " Default: '${SCM_REPO_URL}'"
- echo " -n Native build options to be passed to Quarkus native build process"
- echo " -o Native build options to be passed to Spring native build process"
- echo " -p Enable profiling with async profiler"
- echo " Accepted values: none, jfr, flamegraph"
- echo " Default: ${PROFILER}"
- echo " -q The Quarkus version to use"
- echo " Default: Whatever version is set in pom.xml of the Quarkus app"
- echo " NOTE: Its a good practice to set this manually to ensure proper version"
- echo " -r The runtimes to test, separated by commas"
- echo " Accepted values (1 or more of): quarkus3-jvm, quarkus3-native, spring3-jvm, spring3-jvm-aot, spring3-native"
- echo " Default: 'quarkus3-jvm,quarkus3-native,spring3-jvm,spring3-jvm-aot,spring3-native'"
- echo " -s The Spring Boot version to use"
- echo " Default: Whatever version is set in pom.xml of the Spring Boot app"
- echo " NOTE: Its a good practice to set this manually to ensure proper version"
- echo " -t The tests to run, separated by commas"
- echo " Accepted values (1 or more of): test-build, measure-build-times, measure-time-to-first-request, measure-rss, run-load-test"
- echo " Default: 'test-build,measure-build-times,measure-time-to-first-request,measure-rss,run-load-test'"
- echo " -u The user on to run the benchmark"
- echo " -v JVM Memory setting (i.e. -Xmx -Xmn -Xms)"
- echo " -w Wait time (in seconds) to wait for things like application startup"
- echo " Default: ${WAIT_TIME}"
+ echo " --cpus How many CPUs to allocate to the application"
+ echo " Default: ${CPUS}"
+ echo " --drop-fs-caches Purge/drop OS filesystem caches between iterations"
+ echo " --extra-qdup-args Any extra arguments that need to be passed to qDup ahead of the qDup scripts"
+ echo " NOTE: This is an advanced option. Make sure you know what you are doing when using it."
+ echo " --graalvm-version The GraalVM version to use if running any native tests (from SDKMAN)"
+ echo " Default: ${GRAALVM_VERSION}"
+ echo " --host The HOST to run the benchmarks on"
+ echo " LOCAL is a keyword that can be used to run everything on the local machine"
+ echo " Default: ${HOST}"
+ echo " --iterations The number of iterations to run each test"
+ echo " Default: ${ITERATIONS}"
+ echo " --java-version The Java version to use (from SDKMAN)"
+ echo " Default: ${JAVA_VERSION}"
+ echo " --jvm-args Any JVM args to be passed to the apps"
+ echo " --jvm-memory JVM Memory setting (i.e. -Xmx -Xmn -Xms)"
+ echo " --native-quarkus-build-options Native build options to be passed to Quarkus native build process"
+ echo " --native-spring3-build-options Native build options to be passed to Spring 3.x native build process"
+ echo " --native-spring4-build-options Native build options to be passed to Spring 4.x native build process"
+ echo " --output-dir The directory containing the run output"
+ echo " Default: ${OUTPUT_DIR}"
+ echo " --profiler Enable profiling with async profiler"
+ echo " Accepted values: none, jfr, flamegraph"
+ echo " Default: ${PROFILER}"
+ echo " --quarkus-version The Quarkus version to use"
+ echo " Default: Whatever version is set in pom.xml of the Quarkus app"
+ echo " NOTE: Its a good practice to set this manually to ensure proper version"
+ echo " --repo-branch The branch in the SCM repo"
+ echo " Default: '${SCM_REPO_BRANCH}'"
+ echo " --repo-url The SCM repo url"
+ echo " Default: '${SCM_REPO_URL}'"
+ echo " --runtimes The runtimes to test, separated by commas"
+ echo " Accepted values (1 or more of): quarkus3-jvm, quarkus3-native, spring3-jvm, spring3-jvm-aot, spring3-native"
+ echo " Default: 'quarkus3-jvm,quarkus3-native,spring3-jvm,spring3-jvm-aot,spring3-native,spring4-jvm,spring4-jvm-aot,spring4-native'"
+ echo " --springboot3-version The Spring Boot 3.x version to use"
+ echo " Default: Whatever version is set in pom.xml of the Spring Boot 3 app"
+ echo " NOTE: Its a good practice to set this manually to ensure proper version"
+ echo " --springboot4-version The Spring Boot 4.x version to use"
+ echo " Default: Whatever version is set in pom.xml of the Spring Boot 4 app"
+ echo " NOTE: Its a good practice to set this manually to ensure proper version"
+ echo " --tests The tests to run, separated by commas"
+ echo " Accepted values (1 or more of): test-build, measure-build-times, measure-time-to-first-request, measure-rss, run-load-test"
+ echo " Default: 'test-build,measure-build-times,measure-time-to-first-request,measure-rss,run-load-test'"
+ echo " --user The user on to run the benchmark"
+ echo " --wait-time Wait time (in seconds) to wait for things like application startup"
+ echo " Default: ${WAIT_TIME}"
}
exit_abnormal() {
@@ -65,16 +69,6 @@ validate_values() {
exit_abnormal
fi
- if [ -z "$QUARKUS_VERSION" ]; then
- echo "!! [ERROR] Please set the QUARKUS_VERSION!!"
- exit_abnormal
- fi
-
- if [ -z "$SPRING_BOOT_VERSION" ]; then
- echo "!! [ERROR] Please set the SPRING_BOOT_VERSION!!"
- exit_abnormal
- fi
-
if [ "$HOST" != "LOCAL" -a -z "$USER" ]; then
echo "!! [ERROR] Please set the USER!!"
exit_abnormal
@@ -100,11 +94,13 @@ print_values() {
echo " ITERATIONS: $ITERATIONS"
echo " JAVA_VERSION: $JAVA_VERSION"
echo " NATIVE_QUARKUS_BUILD_OPTIONS: $NATIVE_QUARKUS_BUILD_OPTIONS"
- echo " NATIVE_SPRING_BUILD_OPTIONS: $NATIVE_SPRING_BUILD_OPTIONS"
+ echo " NATIVE_SPRING3_BUILD_OPTIONS: $NATIVE_SPRING3_BUILD_OPTIONS"
+ echo " NATIVE_SPRING4_BUILD_OPTIONS: $NATIVE_SPRING4_BUILD_OPTIONS"
echo " PROFILER: $PROFILER"
echo " QUARKUS_VERSION: $QUARKUS_VERSION"
echo " RUNTIMES: ${RUNTIMES[@]}"
- echo " SPRING_BOOT_VERSION: $SPRING_BOOT_VERSION"
+ echo " SPRING_BOOT3_VERSION: $SPRING_BOOT3_VERSION"
+ echo " SPRING_BOOT4_VERSION: $SPRING_BOOT4_VERSION"
echo " TESTS_TO_RUN: ${TESTS_TO_RUN[@]}"
echo " USER: $USER"
echo " JVM_MEMORY: $JVM_MEMORY"
@@ -190,10 +186,12 @@ ${JBANG_CMD} qDup@hyperfoil \
-S config.resources.cpu.app="${app_cpus}" \
-S config.resources.cpu.db="${db_cpus}" \
-S config.resources.cpu.load_generator="${load_gen_cpus}" \
- -S config.springboot.version=${SPRING_BOOT_VERSION} \
+ -S config.springboot3.version=${SPRING_BOOT3_VERSION} \
+ -S config.springboot4.version=${SPRING_BOOT4_VERSION} \
-S config.jvm.memory="${JVM_MEMORY}" \
-S config.quarkus.version=${QUARKUS_VERSION} \
- -S config.springboot.native_build_options="${NATIVE_SPRING_BUILD_OPTIONS}" \
+ -S config.springboot3.native_build_options="${NATIVE_SPRING3_BUILD_OPTIONS}" \
+ -S config.springboot4.native_build_options="${NATIVE_SPRING4_BUILD_OPTIONS}" \
-S config.profiler.events=cpu \
-S config.repo.branch=${SCM_REPO_BRANCH} \
-S config.repo.url=${SCM_REPO_URL} \
@@ -217,12 +215,14 @@ HOST="LOCAL"
ITERATIONS="3"
JAVA_VERSION="25.0.1-tem"
NATIVE_QUARKUS_BUILD_OPTIONS=""
-NATIVE_SPRING_BUILD_OPTIONS=""
+NATIVE_SPRING3_BUILD_OPTIONS=""
+NATIVE_SPRING4_BUILD_OPTIONS=""
PROFILER="none"
QUARKUS_VERSION=""
-ALLOWED_RUNTIMES=("quarkus3-jvm" "quarkus3-native" "spring3-jvm" "spring3-jvm-aot" "spring3-native")
+ALLOWED_RUNTIMES=("quarkus3-jvm" "quarkus3-native" "spring3-jvm" "spring3-jvm-aot" "spring3-native" "spring4-jvm" "spring4-jvm-aot" "spring4-native")
RUNTIMES=${ALLOWED_RUNTIMES[@]}
-SPRING_BOOT_VERSION=""
+SPRING_BOOT3_VERSION=""
+SPRING_BOOT4_VERSION=""
ALLOWED_TESTS_TO_RUN=("test-build" "measure-build-times" "measure-time-to-first-request" "measure-rss" "run-load-test")
TESTS_TO_RUN=${ALLOWED_TESTS_TO_RUN[@]}
USER=""
@@ -233,96 +233,155 @@ JVM_ARGS=""
EXTRA_QDUP_ARGS=""
OUTPUT_DIR="/tmp"
-# Process the inputs
-while getopts "a:b:c:de:f:g:h:i:j:l:n:o:p:q:r:s:t:u:v:w:" option; do
- case $option in
- a) JVM_ARGS=$OPTARG
+# Process the inputs - Manual parsing for portability
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --jvm-args)
+ JVM_ARGS="$2"
+ shift 2
;;
- b) SCM_REPO_BRANCH=$OPTARG
+ --repo-branch)
+ SCM_REPO_BRANCH="$2"
+ shift 2
;;
- c) CPUS=$OPTARG
+ --cpus)
+ CPUS="$2"
+ shift 2
;;
- d) DROP_OS_FILESYSTEM_CACHES=true
+ --drop-fs-caches)
+ DROP_OS_FILESYSTEM_CACHES=true
+ shift
;;
- e) EXTRA_QDUP_ARGS=$OPTARG
+ --extra-qdup-args)
+ EXTRA_QDUP_ARGS="$2"
+ shift 2
;;
- f) OUTPUT_DIR=$OPTARG
+ --output-dir)
+ OUTPUT_DIR="$2"
+ shift 2
;;
- g) GRAALVM_VERSION=$OPTARG
+ --graalvm-version)
+ GRAALVM_VERSION="$2"
+ shift 2
;;
- h) HOST=$OPTARG
+ --host)
+ HOST="$2"
+ shift 2
;;
- i) ITERATIONS=$OPTARG
+ --iterations)
+ ITERATIONS="$2"
+ shift 2
;;
- j) JAVA_VERSION=$OPTARG
+ --java-version)
+ JAVA_VERSION="$2"
+ shift 2
;;
- l) SCM_REPO_URL=$OPTARG
+ --repo-url)
+ SCM_REPO_URL="$2"
+ shift 2
;;
- n) NATIVE_QUARKUS_BUILD_OPTIONS=$OPTARG
+ --native-quarkus-build-options)
+ NATIVE_QUARKUS_BUILD_OPTIONS="$2"
+ shift 2
;;
- o) NATIVE_SPRING_BUILD_OPTIONS=$OPTARG
+ --native-spring3-build-options)
+ NATIVE_SPRING3_BUILD_OPTIONS="$2"
+ shift 2
;;
- p) if [[ "$OPTARG" =~ ^(none|jfr|flamegraph)$ ]]; then
- PROFILER=$OPTARG
- else
- echo "!! [ERROR] -p option must be one of (none, jfr, flamegraph)!!"
- exit_abnormal
- fi
+ --native-spring4-build-options)
+ NATIVE_SPRING4_BUILD_OPTIONS="$2"
+ shift 2
;;
- q) QUARKUS_VERSION=$OPTARG
+ --profiler)
+ if [[ "$2" =~ ^(none|jfr|flamegraph)$ ]]; then
+ PROFILER="$2"
+ else
+ echo "!! [ERROR] --profiler option must be one of (none, jfr, flamegraph)!!"
+ exit_abnormal
+ fi
+ shift 2
;;
- r) rt=($(IFS=','; echo $OPTARG))
+ --quarkus-version)
+ QUARKUS_VERSION="$2"
+ shift 2
+ ;;
+
+ --runtimes)
+ rt=($(IFS=','; echo $2))
+
+ for item in "${rt[@]}"; do
+ if [[ ! "${ALLOWED_RUNTIMES[@]}" =~ "${item}" ]]; then
+ echo "!! [ERROR] --runtimes option must contain 1 or more of [${ALLOWED_RUNTIMES[@]}]!!"
+ exit_abnormal
+ fi
+ done
- for item in "${rt[@]}"; do
- if [[ ! "${ALLOWED_RUNTIMES[@]}" =~ "${item}" ]]; then
- echo "!! [ERROR] -r option must contain 1 or more of [${ALLOWED_RUNTIMES[@]}]!!"
- exit_abnormal
- fi
- done
+ RUNTIMES=${rt[@]}
+ shift 2
+ ;;
- RUNTIMES=${rt[@]}
+ --springboot3-version)
+ SPRING_BOOT3_VERSION="$2"
+ shift 2
;;
- s) SPRING_BOOT_VERSION=$OPTARG
+ --springboot4-version)
+ SPRING_BOOT4_VERSION="$2"
+ shift 2
;;
- t) ttr=($(IFS=','; echo $OPTARG))
+ --tests)
+ ttr=($(IFS=','; echo $2))
+
+ for item in "${ttr[@]}"; do
+ if [[ ! "${ALLOWED_TESTS_TO_RUN[@]}" =~ "${item}" ]]; then
+ echo "!! [ERROR] --tests option must contain 1 or more of [${ALLOWED_TESTS_TO_RUN[@]}]!!"
+ exit_abnormal
+ fi
+ done
- for item in "${ttr[@]}"; do
- if [[ ! "${ALLOWED_TESTS_TO_RUN[@]}" =~ "${item}" ]]; then
- echo "!! [ERROR] -t option must contain 1 or more of [${ALLOWED_TESTS_TO_RUN[@]}]!!"
- exit_abnormal
- fi
- done
+ TESTS_TO_RUN=${ttr[@]}
+ shift 2
+ ;;
- TESTS_TO_RUN=${ttr[@]}
+ --user)
+ USER="$2"
+ shift 2
;;
- u) USER=$OPTARG
+ --jvm-memory)
+ JVM_MEMORY="$2"
+ shift 2
;;
- v) JVM_MEMORY=$OPTARG
+ --wait-time)
+ WAIT_TIME="$2"
+ shift 2
;;
- w) WAIT_TIME=$OPTARG
+ -*)
+ echo "!! [ERROR] Unknown option: $1"
+ exit_abnormal
;;
- *) exit_abnormal
+ *)
+ echo "!! [ERROR] Unexpected argument: $1"
+ exit_abnormal
;;
esac
done
diff --git a/springboot3/pom.xml b/springboot3/pom.xml
index c552656..68a1b7c 100644
--- a/springboot3/pom.xml
+++ b/springboot3/pom.xml
@@ -85,9 +85,8 @@
enhance
- true
- true
- true
+ false
+ false
diff --git a/springboot4/.mvn/wrapper/maven-wrapper.properties b/springboot4/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..c0bcafe
--- /dev/null
+++ b/springboot4/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,3 @@
+wrapperVersion=3.3.4
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
diff --git a/springboot4/README.md b/springboot4/README.md
new file mode 100644
index 0000000..58986d3
--- /dev/null
+++ b/springboot4/README.md
@@ -0,0 +1,20 @@
+## JVM Mode
+To compile for JVM mode:
+`./mvnw clean package`
+
+To run in JVM Mode:
+`java -jar target/app.jar`
+
+## AOT on JVM mode
+To compile for AOT mode:
+`./mvnw clean compile spring-boot:process-aot package`
+
+To run in AOT mode:
+`java -Dspring.aot.enabled=true -jar target/springboot4.jar`
+
+## Native Mode
+To compile for native mode:
+`./mvnw clean native:compile -Pnative`
+
+To run in native mode:
+`target/springboot4`
\ No newline at end of file
diff --git a/springboot4/mvnw b/springboot4/mvnw
new file mode 100755
index 0000000..bd8896b
--- /dev/null
+++ b/springboot4/mvnw
@@ -0,0 +1,295 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.4
+#
+# Optional ENV vars
+# -----------------
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
+ fi
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
+ fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
+ done
+ printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
+
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+scriptDir="$(dirname "$0")"
+scriptName="$(basename "$0")"
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
+else
+ die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+actualDistributionDir=""
+
+# First try the expected directory name (for regular distributions)
+if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
+ if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$distributionUrlNameMain"
+ fi
+fi
+
+# If not found, search for any directory with the Maven executable (for snapshots)
+if [ -z "$actualDistributionDir" ]; then
+ # enable globbing to iterate over items
+ set +f
+ for dir in "$TMP_DOWNLOAD_DIR"/*; do
+ if [ -d "$dir" ]; then
+ if [ -f "$dir/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$(basename "$dir")"
+ break
+ fi
+ fi
+ done
+ set -f
+fi
+
+if [ -z "$actualDistributionDir" ]; then
+ verbose "Contents of $TMP_DOWNLOAD_DIR:"
+ verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
+ die "Could not find Maven distribution directory in extracted archive"
+fi
+
+verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/springboot4/mvnw.cmd b/springboot4/mvnw.cmd
new file mode 100644
index 0000000..92450f9
--- /dev/null
+++ b/springboot4/mvnw.cmd
@@ -0,0 +1,189 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.4
+@REM
+@REM Optional ENV vars
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+
+$MAVEN_M2_PATH = "$HOME/.m2"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
+}
+
+if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
+ New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
+}
+
+$MAVEN_WRAPPER_DISTS = $null
+if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
+ $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
+} else {
+ $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
+}
+
+$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+$actualDistributionDir = ""
+
+# First try the expected directory name (for regular distributions)
+$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
+$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
+if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
+ $actualDistributionDir = $distributionUrlNameMain
+}
+
+# If not found, search for any directory with the Maven executable (for snapshots)
+if (!$actualDistributionDir) {
+ Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
+ $testPath = Join-Path $_.FullName "bin/$MVN_CMD"
+ if (Test-Path -Path $testPath -PathType Leaf) {
+ $actualDistributionDir = $_.Name
+ }
+ }
+}
+
+if (!$actualDistributionDir) {
+ Write-Error "Could not find Maven distribution directory in extracted archive"
+}
+
+Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/springboot4/pom.xml b/springboot4/pom.xml
new file mode 100644
index 0000000..b305d48
--- /dev/null
+++ b/springboot4/pom.xml
@@ -0,0 +1,129 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.0
+
+
+ org.acme
+ springboot4
+ 1.0
+ springboot4
+ Spring Boot 4 example
+
+ 21
+ 4.0.0
+ 7.1.9.Final
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-webmvc
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+ org.hibernate.orm
+ hibernate-jcache
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+
+
+ com.github.ben-manes.caffeine
+ jcache
+
+
+ org.springframework.boot
+ spring-boot-starter-validation-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-webmvc-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator-test
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ org.testcontainers
+ testcontainers-junit-jupiter
+ test
+
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+ org.testcontainers
+ testcontainers-postgresql
+ test
+
+
+
+ springboot4
+
+
+ org.hibernate.orm
+ hibernate-maven-plugin
+ ${hibernate.version}
+
+
+ enhance
+
+ enhance
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+ ${spring-boot.version}
+
+
+
+
+ org.graalvm.buildtools
+ native-maven-plugin
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/springboot4/src/main/java/org/acme/L2CacheConfiguration.java b/springboot4/src/main/java/org/acme/L2CacheConfiguration.java
new file mode 100644
index 0000000..9f00f5a
--- /dev/null
+++ b/springboot4/src/main/java/org/acme/L2CacheConfiguration.java
@@ -0,0 +1,59 @@
+package org.acme;
+
+import java.net.URI;
+
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.spi.CachingProvider;
+
+import org.springframework.boot.hibernate.autoconfigure.HibernatePropertiesCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.github.benmanes.caffeine.jcache.configuration.CaffeineConfiguration;
+import com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider;
+
+@Configuration
+public class L2CacheConfiguration {
+ @Bean
+ public CacheManager jCacheManager() {
+ CachingProvider provider = Caching.getCachingProvider(CaffeineCachingProvider.class.getName());
+ CacheManager cacheManager = provider.getCacheManager(URI.create("caffeine://default"), getClass().getClassLoader());
+
+ // Create default cache configuration with store-by-reference
+ createCache(cacheManager, "default", createCaffeineConfig());
+
+ // Create cache for Store entity with expiration and size limits
+ createCache(cacheManager, "org.acme.domain.Store",
+ createCaffeineConfig());
+
+ // Create cache for StoreFruitPrice.store association with expiration and size limits
+ createCache(cacheManager, "org.acme.domain.StoreFruitPrice.store",
+ createCaffeineConfig());
+
+ return cacheManager;
+ }
+
+ private void createCache(CacheManager cacheManager, String cacheName, CaffeineConfiguration