diff --git a/.github/workflows/manual-publish-image.yml b/.github/workflows/manual-publish-image.yml
index 64f87ff..affd3cf 100644
--- a/.github/workflows/manual-publish-image.yml
+++ b/.github/workflows/manual-publish-image.yml
@@ -40,7 +40,7 @@ jobs:
cache: 'maven'
- name: Build Native Binary
- run: ./mvnw clean package -Pnative,prod -DskipTests
+ run: ./mvnw clean package -Pprod -DskipTests
- name: Log in to the Container registry
uses: docker/login-action@v3
@@ -56,8 +56,8 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
- file: ./Dockerfile.native
- build-args: BINARY=prebuilt
+ file: ./Dockerfile
+ build-args: JAR_SOURCE=build
platforms: ${{ matrix.platform }}
push: true
tags: |
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 3b48ec2..c130942 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -133,7 +133,7 @@ jobs:
run: mvn -B versions:set -DnewVersion=${{ needs.prepare-release.outputs.release-version }} -DgenerateBackupPoms=false
- name: Build Native Binary
- run: ./mvnw clean package -Pnative,prod -DskipTests
+ run: ./mvnw clean package -Pprod -DskipTests
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
@@ -149,8 +149,8 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
- file: ./Dockerfile.native
- build-args: BINARY=prebuilt
+ file: ./Dockerfile
+ build-args: JAR_SOURCE=build
platforms: ${{ matrix.platform }}
push: true
tags: |
diff --git a/Dockerfile b/Dockerfile
index 4bc7d90..8410fd9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,3 +1,23 @@
+# Build arg to select JAR source: "build" (compile from source) or "prebuilt" (use pre-built JAR)
+ARG JAR_SOURCE=build
+
+# Builder stage — compiles JAR from source with frontend (default)
+FROM eclipse-temurin:25-jdk AS build
+WORKDIR /build
+COPY .mvn .mvn
+COPY mvnw pom.xml ./
+RUN ./mvnw dependency:go-offline -B
+COPY src src
+RUN ./mvnw clean package -Pprod -DskipTests \
+ && cp target/turtorial-*.jar /turtorial.jar
+
+# Prebuilt stage — copies JAR from build context (CI or local pre-built)
+FROM scratch AS prebuilt
+COPY target/turtorial-*.jar /turtorial.jar
+
+# Select JAR source based on ARG (BuildKit skips unused stages)
+FROM ${JAR_SOURCE} AS jar
+
FROM ubuntu:24.04 AS base
# Set install locations
@@ -22,9 +42,15 @@ RUN apt-get update && apt-get install -y \
nano \
&& rm -rf /var/lib/apt/lists/*
-# Install Temurin JDK 25
-RUN mkdir -p $JAVA_HOME \
- && curl -L "https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.1%2B8/OpenJDK25U-jdk_aarch64_linux_hotspot_25.0.1_8.tar.gz" | tar -xz -C $JAVA_HOME --strip-components=1
+# Install Temurin JDK 25 (detect architecture automatically)
+RUN ARCH=$(uname -m) \
+ && case "$ARCH" in \
+ x86_64) TEMURIN_ARCH="x64" ;; \
+ aarch64) TEMURIN_ARCH="aarch64" ;; \
+ *) echo "Unsupported architecture: $ARCH" && exit 1 ;; \
+ esac \
+ && mkdir -p $JAVA_HOME \
+ && curl -L "https://api.adoptium.net/v3/binary/latest/25/ga/linux/${TEMURIN_ARCH}/jdk/hotspot/normal/eclipse?project=jdk" | tar -xz -C $JAVA_HOME --strip-components=1
# Install Maven
RUN mkdir -p $MAVEN_HOME \
@@ -48,10 +74,8 @@ RUN mkdir -p /home/turtorial/.ssh \
# Create app directory
WORKDIR /app
-# Copy the built application
-# Assumes `mvn clean package` has been run locally
-ARG JAR_FILE=target/turtorial-*-SNAPSHOT.jar
-COPY ${JAR_FILE} /app/turtorial.jar
+# Copy the JAR from the selected source
+COPY --from=jar /turtorial.jar /app/turtorial.jar
# Change ownership of the app directory
RUN chown -R turtorial:turtorial /app
@@ -69,4 +93,4 @@ EXPOSE 8080
RUN mkdir -p /app/lessons
# Run the application
-ENTRYPOINT ["java", "-jar", "/app/turtorial.jar"]
\ No newline at end of file
+ENTRYPOINT ["java", "-jar", "/app/turtorial.jar"]
diff --git a/examples/moderne-spring-boot-migration/Dockerfile b/examples/moderne-spring-boot-migration/Dockerfile
index 759f16b..89d9e9a 100644
--- a/examples/moderne-spring-boot-migration/Dockerfile
+++ b/examples/moderne-spring-boot-migration/Dockerfile
@@ -12,6 +12,16 @@ FROM turtorial:latest AS base
################################################################################
USER root
+
+# # Install Temurin JDK 17 (needed for Spring Boot 2.7 migration workshop)
+# # Note: turtorial:latest installs Java 25 to /opt/java/openjdk
+# RUN mkdir -p /opt/java/openjdk17 \
+# && curl -L "https://api.adoptium.net/v3/binary/latest/17/ga/linux/aarch64/jdk/hotspot/normal/eclipse" | tar -xz -C /opt/java/openjdk17 --strip-components=1
+
+# # Set JAVA_HOME to Java 17 for the workshop exercises
+# ENV JAVA_HOME=/opt/java/openjdk17
+# ENV PATH=$JAVA_HOME/bin:$PATH
+
ARG MODERNE_CLI_STAGE=stable
ARG MODERNE_CLI_VERSION
# Set the environment variable MODERNE_CLI_VERSION
@@ -67,4 +77,10 @@ RUN mkdir -p /home/turtorial/workspaces/step1 \
# Copy over our lessons
COPY --chown=turtorial:turtorial lessons /app/lessons
-WORKDIR /home/turtorial
\ No newline at end of file
+WORKDIR /home/turtorial
+
+ENV SPRING_PROFILES_ACTIVE=prod
+
+# Run the application
+# ENTRYPOINT ["/app/turtorial"]
+ENTRYPOINT ["java", "-jar", "/app/turtorial.jar"]
\ No newline at end of file
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/01-01-overview.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/01-01-overview.mdx
new file mode 100644
index 0000000..cdead0a
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/01-01-overview.mdx
@@ -0,0 +1,18 @@
+---
+title: Workshop Overview
+section: Introduction
+order: 1
+---
+# Preparing for Spring Boot Migration
+
+Spring Boot migrations are rarely just a version bump. You need to understand your repositories, identify hidden dependencies, align build tooling, and plan release waves so downstream services can upgrade safely. This workshop walks you through a realistic, end-to-end prep flow using the Moderne CLI so you can move into a Spring Boot 4 migration with fewer surprises.
+
+## Workshop Overview
+
+In this workshop, you will:
+
+1. **Assess**: Run an initial migration and gather code insight data.
+2. **Plan**: Analyze dependencies and organize upgrade waves.
+3. **Baseline**: Normalize Maven, Java, and Spring Boot versions.
+4. **Smoke Test**: Upgrade to Java 17 and run a Spring Boot 4 smoke test.
+5. **Migrate**: Finish the migration in waves using custom recipes.
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/01-02-env-setup.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/01-02-env-setup.mdx
new file mode 100644
index 0000000..28644cd
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/01-02-env-setup.mdx
@@ -0,0 +1,36 @@
+---
+title: Environment Setup
+section: Introduction
+order: 2
+---
+# Environment Setup
+
+This tutorial runs in a pre-configured environment with the Moderne CLI (`mod`) and necessary build tools (Maven, Java).
+
+The example repositories have been cloned into your workspace at `/home/turtorial/moderne-migration-practice` (referred to as `$WORKSHOP`).
+
+Let's start by setting up some environment variables to make the commands easier to run.
+
+```bash
+export WORKSHOP=/home/turtorial/moderne-migration-practice
+export WORKSPACE=/home/turtorial/workspaces/migration-practice-workspace
+export PROJECTS=/home/turtorial/projects
+```
+
+Now, let's create our workspace directory and sync the repositories.
+
+```bash
+mkdir -p $WORKSPACE
+cd $WORKSPACE
+
+# Sync the repositories from the workshop configuration
+mod git sync csv $WORKSPACE $WORKSHOP/repos.csv --with-sources
+```
+
+Verify that the repositories are synced:
+
+```bash
+ls -F $WORKSPACE
+```
+
+You should see a directory structure corresponding to the repositories.
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/01-03-build-lsts.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/01-03-build-lsts.mdx
new file mode 100644
index 0000000..f0aa401
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/01-03-build-lsts.mdx
@@ -0,0 +1,14 @@
+---
+title: Build LSTs
+section: Introduction
+order: 3
+---
+# Build LSTs
+
+To run recipes, we need to build Lossless Semantic Trees (LSTs) for the projects.
+
+```bash
+mod build $WORKSPACE
+```
+
+This might take a few minutes as it compiles the projects and generates the LSTs.
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/01-04-install-recipes.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/01-04-install-recipes.mdx
new file mode 100644
index 0000000..2b96de1
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/01-04-install-recipes.mdx
@@ -0,0 +1,22 @@
+---
+title: Install Recipes
+section: Introduction
+order: 4
+---
+# Install Recipes
+
+We'll install the necessary recipes for this workshop.
+
+```bash
+mod config recipes jar install \
+ io.moderne.recipe:rewrite-spring:0.19.0 \
+ org.openrewrite.recipe:rewrite-migrate-java:3.24.0 \
+ org.openrewrite.recipe:rewrite-java-dependencies:1.48.0 \
+ org.openrewrite:rewrite-java:8.69.0 \
+ org.openrewrite:rewrite-maven:8.69.0 \
+ io.moderne.recipe:rewrite-devcenter:1.13.1 \
+ org.openrewrite.recipe:rewrite-spring:6.21.0 \
+ org.openrewrite.recipe:rewrite-testing-frameworks:3.24.0
+```
+
+Now you are ready to start the assessment!
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/01-setup.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/01-setup.mdx
deleted file mode 100644
index d72b6db..0000000
--- a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/01-setup.mdx
+++ /dev/null
@@ -1,49 +0,0 @@
----
-title: Setup
----
-# Setup
-
-> Note: We'll use a couple of conventions in this document to refer to some common directories. `$WORKSHOP` will refer to the root of this repository. `$WORKSPACE` will refer to the directory you're cloning repos into and running `mod` commands against. `$PROJECTS` refers to a directory outside of `$WORKSPACE` where you can clone other repositories to work on separately. If you want to make this simpler and run commands copied directly from this workshop guide, you can use `export WORKSPACE=~/workspaces/migration_workshop`, `export WORKSHOP=~/projects/moderne-migration-practice`, and `export PROJECTS=~/projects` in your shell session. Replace the values of these variables with their real locations on your hard drive.
-
-Let's start by using the Moderne CLI to clone all of our example repositories into an empty directory:
-
-```bash
-# Navigate to your empty workspace (make sure you've set `$WORKSPACE` in your shell previously, or just replace it with the actual place you want to go)
-cd $WORKSPACE
-
-# Clone all of our example repositories into the workspace with their source code so that we can apply suggested changes from recipes
-# We will use this environment variable explicitly going forward so you can run `mod` commands from anywhere, but if you navigate to the `$WORKSPACE` path with the previous command and run `mod` from there, you can replace `$WORKSPACE` with `.` for future `mod` commands
-mod git sync csv $WORKSPACE $WORKSHOP/repos.csv --with-sources
-```
-
-This `repos.csv` lists all of our example repositories, and you can view the final workspace structure to see that you have a directory for the GitHub org (or user) with the repositories inside:
-
-```bash
-tree -d $WORKSPACE. -L 3
-```
-
-This repository includes some helper scripts to run commands like testing, releasing, and doing common git actions across your repositories. The software development lifecycle of building and releasing new versions of these repositories is usually handled by your existing process outside of OpenRewrite and Moderne, but in this workshop you can simulate a release with the included `release.sh` script. Since all of our projects currently depend on a release version 1.0.0 of each other and those don't exist yet, go ahead and run a first release to get everything building:
-
-```bash
-# Release
-$WORKSHOP/release.sh
-```
-
-This will automatically install the current non-SNAPSHOT version of each repository into your local Maven cache. It will then bump the project's version to the next available minor SNAPSHOT version, ready for you to make more changes.
-
-Now we can build our first Lossless Semantic Trees (LSTs) so we can run OpenRewrite recipes on the synced repositories. This command may take a few minutes to run as it compiles the projects and builds the LSTs for each project:
-
-```bash
-# Build an LST for each project
-mod build $WORKSPACE
-```
-
-To make sure we have all the OpenRewrite recipes we will need for this workshop, we can install the following recipe artifacts:
-
-```bash
-# For a deterministic workshop, this clears any installed recipes first
-mod config recipes delete
-
-# Install the necessary recipes for this workshop from the public Maven repository
-mod config recipes jar install io.moderne.recipe:rewrite-spring:0.19.0 org.openrewrite.recipe:rewrite-migrate-java:3.24.0 org.openrewrite.recipe:rewrite-java-dependencies:1.48.0 org.openrewrite:rewrite-java:8.69.0 org.openrewrite:rewrite-maven:8.69.0 io.moderne.recipe:rewrite-devcenter:1.13.1 org.openrewrite.recipe:rewrite-spring:6.21.0 org.openrewrite.recipe:rewrite-testing-frameworks:3.24.0
-```
\ No newline at end of file
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-01-dry-run.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-01-dry-run.mdx
new file mode 100644
index 0000000..6af937f
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-01-dry-run.mdx
@@ -0,0 +1,18 @@
+---
+title: Full Migration Dry Run
+section: Assessment
+order: 5
+---
+# Exercise 1: Run the Full Migration Recipe (Dry Run)
+
+In this module, you will run the Spring Boot 4 migration recipe to find the biggest risks and blockers. The idea is to see what breaks before investing time in fixes.
+
+First, let's run a "dry run" of the full Spring Boot 4 migration recipe. This will not modify the code but will identify what changes would be made and what errors might occur.
+
+```bash
+mod run $WORKSPACE --recipe io.moderne.java.spring.boot4.UpgradeSpringBoot_4_0 --dry-run
+```
+
+Review the output. You might see warnings or errors related to missing types or compilation failures. This is expected!
+
+Specifically, look for errors related to `QueryDSL` (generated code issues) or missing dependencies.
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-02-java-version.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-02-java-version.mdx
new file mode 100644
index 0000000..8899fbc
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-02-java-version.mdx
@@ -0,0 +1,20 @@
+---
+title: Identify Java Version
+section: Assessment
+order: 6
+---
+# Exercise 2: Identify Java Version Usage
+
+Let's see what Java versions are currently in use across the repositories.
+
+```bash
+mod run $WORKSPACE --recipe org.openrewrite.java.migrate.search.PlanJavaMigration --dry-run
+```
+
+To see the data table produced by this recipe:
+
+```bash
+mod study $WORKSPACE --last-recipe-run --data-table org.openrewrite.java.migrate.table.JavaVersionMigrationPlan
+```
+
+You should see that most projects are on Java 8.
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-03-spring-boot-version.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-03-spring-boot-version.mdx
new file mode 100644
index 0000000..ab15d30
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-03-spring-boot-version.mdx
@@ -0,0 +1,21 @@
+---
+title: Inspect Spring Boot Version
+section: Assessment
+order: 7
+---
+# Exercise 3: Inspect Spring Boot Versions
+
+Now, let's find out which Spring Boot versions are currently used.
+
+```bash
+mod run $WORKSPACE --recipe org.openrewrite.java.dependencies.DependencyInsight --dry-run \
+ --recipe-option groupPattern=org.springframework.boot \
+ --recipe-option artifactPattern=* \
+ --recipe-option scope=runtime
+```
+
+Check the results:
+
+```bash
+mod study $WORKSPACE --last-recipe-run --data-table org.openrewrite.maven.table.DependenciesInUse
+```
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-04-javax-usage.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-04-javax-usage.mdx
new file mode 100644
index 0000000..b0a6c44
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-04-javax-usage.mdx
@@ -0,0 +1,19 @@
+---
+title: Find javax.* Usage
+section: Assessment
+order: 8
+---
+# Exercise 4: Find `javax.*` Usage
+
+Spring Boot 3+ (and thus 4) moves from `javax.*` to `jakarta.*`. High usage of `javax.*` means more work.
+
+```bash
+mod run $WORKSPACE --recipe org.openrewrite.java.search.FindTypes --dry-run \
+ --recipe-option fullyQualifiedTypeName=javax..*
+```
+
+Check the results:
+
+```bash
+mod study $WORKSPACE --last-recipe-run --data-table org.openrewrite.java.search.TypeUses
+```
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-05-code-generators.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-05-code-generators.mdx
new file mode 100644
index 0000000..130cc7a
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-05-code-generators.mdx
@@ -0,0 +1,22 @@
+---
+title: Locate Code Generators
+section: Assessment
+order: 9
+---
+# Exercise 5: Locate Code Generators (QueryDSL)
+
+Code generators like QueryDSL can be major blockers. Let's find where they are used.
+
+```bash
+mod run $WORKSPACE --recipe org.openrewrite.maven.search.FindPlugin --dry-run \
+ --recipe-option groupId=com.mysema.maven \
+ --recipe-option artifactId=apt-maven-plugin
+```
+
+Check the results:
+
+```bash
+mod study $WORKSPACE --last-recipe-run --data-table org.openrewrite.maven.table.SearchResults
+```
+
+You should see usage in several services.
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-06-assessment-quiz.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-06-assessment-quiz.mdx
new file mode 100644
index 0000000..f0e53fa
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-06-assessment-quiz.mdx
@@ -0,0 +1,15 @@
+---
+title: Assessment Quiz
+section: Assessment
+order: 10
+---
+# Quiz
+
+Why is it important to run a dry run and code insight recipes before starting the actual migration?
+
+1. To generate more code.
+2. To identify blockers, dependencies, and plan the migration strategy.
+3. To fix bugs automatically without checking.
+4. To update the Java version immediately.
+
+**Correct Answer**: 2
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-dry-run-full-migration.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-dry-run-full-migration.mdx
deleted file mode 100644
index d77aeb2..0000000
--- a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/02-dry-run-full-migration.mdx
+++ /dev/null
@@ -1,56 +0,0 @@
----
-title: Dry Run Full Migration
----
-
-# Step 1: Run the full migration
-
-First up, you can always start by running the full migration recipe that we ultimately want to finish with. This often gives us some information on some of the obvious pitfalls that we might run into, including incompatible libraries or additional customizations that we need to make to the recipe. Go ahead and run the upgrade Spring Boot 4.0 recipe now:
-
-```bash
-# Run the recipe
-mod run $WORKSPACE --recipe io.moderne.java.spring.boot4.UpgradeSpringBoot_4_0
-
-# Apply the suggested changes to all projects
-mod git apply $WORKSPACE --last-recipe-run
-
-# Build the projects to see if they compile
-$WORKSHOP/build.sh
-```
-
-> Note: To ensure reproducible builds, projects refer to each other with specific release versions - not SNAPSHOTs or other dynamic references. This simulates an environment in a large organization where each of these repositories is owned by different teams and released independently. This probably isn't the case for _every_ repository in your portfolio - there's likely collections of related repositories that are all built and released together as a constellation of services. In our workshop, we'll simplify this to "every repository is independent from the others" to make sure we practice this particular speed bump.
-
-Take a second to look at the output for the failed Maven builds. Why doesn't this work?
-
-While every repository presents its own unique challenges, there are common speed bumps along the way that we can look for in any set of depositors. These include:
-
-- Inconsistent or brittle build tool configuration
-- Incompatible build tool versions (both the tools themselves and their plugins)
-- Third-party dependencies that are incompatible with new versions of Java or upgraded frameworks
-- Dependencies between repositories in the whole set that need to be built in order
-
-In the example applications, we can see that there's actually a number of issues:
-
-- We're failing to build some classes like `QOrder` and `QInventory`. These "Q" classes are coming from a code generator called QueryDSL, and code generators are a class of tools that are generally problematic during migrations. They often generate code that's specifically tuned for a particular version of Java or frameworks like Spring. Upgrading those runtimes often require an update to the code generator.
-- We're seeing failures compiling test classes with errors like `package org.springframework.boot.test.autoconfigure.web.servlet does not exist`. Upgrading Spring also includes upgrading to a newer version of JUnit, so this might require us to update our tests, or we might be pulling in outdated testing libraries as dependencies.
-- Errors like `'dependencies.dependency.version' for org.springframework.cloud:spring-cloud-starter-zipkin:jar is missing.` sounds like these used to be managed dependencies in our older Spring Boot versions but now there is no managed version specified in Spring Boot 4.0. This can happen when the dependency has move or been replaced by a different Spring Boot starter, or if the particular functionality was deprecated and removed.
-
-## What makes a third-party library incompatible with newer versions of Java?
-
-While you can run libraries compiled with an older version of Java in newer versions of JVM, the Java runtime has deprecated specific APIs and ultimately removed or refactored those as it has evolved. The major change that many people run into includes many `javax.*` internal APIs, including the Java EE APIs that were ultimately moved out of the JVM entirely and into the Jakarta namespace.
-
-## Why do we care about dependencies between repositories as we're going through a migration?
-
-Large organizations often have repositories that depend on each other, using internal shared libraries to share code and standardize specific functionality. These shared libraries can be owned, built, released, and versioned independently from the repositories that use them, so we need to upgrade these projects _and_ run them through their whole software developer lifecycle to release a new updated version that downstream consumers can move to. This can sometimes be as simple as upgrading a version number in a build manifest, or it can include complex code changes for those consumers to upgrade their code depending on how the libraries are built.
-
-Regardless, in order to upgrade all of our repositories, we often need to find the sets of repositories that are depended on and upgrade them first, then upgrade the rest in the proper sequence. For this workshop, we'll refer to each of these sets of repositories as "waves" of our migration.
-
-We can use the Moderne CLI to apply any command across all of our repositories, including git commands. Go ahead and reset our repositories to a clean state now that we've learned from this experiment:
-
-```bash
-# Restore
-mod exec $WORKSPACE git restore MODERNE_BUILD_TOOL_DIR
-
-# Rebuild the LST
-$WORKSHOP/build.sh
-mod build $WORKSPACE
-```
\ No newline at end of file
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/03-01-generate-dependency-data.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/03-01-generate-dependency-data.mdx
new file mode 100644
index 0000000..1eab178
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/03-01-generate-dependency-data.mdx
@@ -0,0 +1,34 @@
+---
+title: Generate Dependency Data
+section: Wave Planning
+order: 11
+---
+# Exercise 1: Generate Dependency Data
+
+In this module, you'll move from assessment to planning. You will group repositories into “waves” based on dependencies and upgrade them in order.
+
+First, we need to install a recipe that analyzes dependencies to create a wave plan.
+
+Clone the `Release-Train-Metro-Plan` repository:
+
+```bash
+mkdir -p $PROJECTS
+cd $PROJECTS
+git clone https://github.com/MBoegers/Release-Train-Metro-Plan.git
+```
+
+Install the recipe artifact locally:
+
+```bash
+mod config recipes jar install dev.mboegie.rewrite:release-train-metro-plan:RELEASE
+```
+
+Now, run the recipe on your workspace:
+
+```bash
+mod run $WORKSPACE --recipe dev.mboegie.rewrite.releasemetro.ReleaseMetroPlan --parallel
+```
+
+This generates several data tables (e.g., `ParentRelationships`, `ProjectCoordinates`) that can be used to visualize the dependency graph.
+
+In a real-world scenario, you would export these tables and use a tool (like a Jupyter notebook) to generate a "metro map" of your upgrade waves. For this workshop, we have pre-calculated the waves for you.
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/03-02-organize-workspace.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/03-02-organize-workspace.mdx
new file mode 100644
index 0000000..1ecf09d
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/03-02-organize-workspace.mdx
@@ -0,0 +1,31 @@
+---
+title: Organize Workspace
+section: Wave Planning
+order: 12
+---
+# Exercise 2: Organize Workspace by Wave
+
+Based on the dependency analysis, we have identified the following waves:
+
+* **Wave 0**: Independent repositories (libraries).
+* **Wave 1**: Services depending only on Wave 0.
+* **Wave 2**: Services depending on Wave 1.
+* **Wave 3**: Services depending on Wave 2.
+
+We will use a special `repos-waves.csv` file that groups the repositories into these waves. This allows us to target specific waves with our recipes.
+
+Sync the repositories using the wave-aware CSV:
+
+```bash
+mod git sync csv $WORKSPACE $WORKSHOP/repos-waves.csv --with-sources
+```
+
+If prompted to replace the existing organization, confirm with `Y`.
+
+Now, verify the new structure:
+
+```bash
+ls -F $WORKSPACE
+```
+
+You should see directories like `Wave0`, `Wave1`, etc. This structure is critical for running wave-based migrations.
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/03-03-wave-planning-quiz.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/03-03-wave-planning-quiz.mdx
new file mode 100644
index 0000000..d503d9f
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/03-03-wave-planning-quiz.mdx
@@ -0,0 +1,15 @@
+---
+title: Wave Planning Quiz
+section: Wave Planning
+order: 13
+---
+# Quiz
+
+Why do we upgrade in waves?
+
+1. To upgrade everything at once.
+2. To upgrade downstream services before their dependencies.
+3. To ensure that dependencies are upgraded and released before dependent services consume them.
+4. Because it looks cool on a map.
+
+**Correct Answer**: 3
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/04-01-create-baseline-recipe.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/04-01-create-baseline-recipe.mdx
new file mode 100644
index 0000000..f83de00
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/04-01-create-baseline-recipe.mdx
@@ -0,0 +1,41 @@
+---
+title: Create Baseline Recipe
+section: Establish Baseline
+order: 14
+---
+# Exercise 1: Create a Baseline Recipe
+
+In this module, we will normalize our repositories to a consistent baseline: Java 8 and Spring Boot 2.7.
+
+We will create a composite recipe that applies the following:
+* Maven best practices (`org.openrewrite.maven.BestPractices`).
+* Migrate to Java 8 (`org.openrewrite.java.migrate.UpgradeToJava8`).
+* Migrate to Spring Boot 2.7 (`org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7`).
+* Replace `spring-cloud-starter-zipkin` (deprecated) with `spring-cloud-sleuth-zipkin`.
+
+Create a file named `WorkshopBaseline.yml` in your `$WORKSHOP` directory:
+
+```bash
+cat <<'EOF' > $WORKSHOP/WorkshopBaseline.yml
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: com.example.ecom.recipe.SpringBootMigrationWorkshopBaseline
+displayName: Spring Boot Migration Workshop Baseline
+description: Upgrade Java 8, Maven, and Spring Boot 2.7.
+recipeList:
+ - org.openrewrite.maven.BestPractices
+ - org.openrewrite.java.migrate.UpgradeToJava8
+ - org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7
+ - org.openrewrite.maven.ChangeDependencyGroupIdAndArtifactId:
+ oldGroupId: org.springframework.cloud
+ oldArtifactId: spring-cloud-starter-zipkin
+ newGroupId: org.springframework.cloud
+ newArtifactId: spring-cloud-sleuth-zipkin
+EOF
+```
+
+Install the recipe locally:
+
+```bash
+mod config recipes yaml install $WORKSHOP/WorkshopBaseline.yml
+```
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/04-02-apply-baseline.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/04-02-apply-baseline.mdx
new file mode 100644
index 0000000..6956fa0
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/04-02-apply-baseline.mdx
@@ -0,0 +1,24 @@
+---
+title: Apply Baseline
+section: Establish Baseline
+order: 15
+---
+# Exercise 2: Apply the Baseline Recipe
+
+First, let's build the LSTs for the new wave structure:
+
+```bash
+mod build $WORKSPACE
+```
+
+Now, run the baseline recipe across the entire workspace (all waves):
+
+```bash
+mod run $WORKSPACE --recipe com.example.ecom.recipe.SpringBootMigrationWorkshopBaseline
+```
+
+Apply the changes:
+
+```bash
+mod git apply $WORKSPACE --last-recipe-run
+```
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/04-03-build-and-release.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/04-03-build-and-release.mdx
new file mode 100644
index 0000000..8794cfd
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/04-03-build-and-release.mdx
@@ -0,0 +1,33 @@
+---
+title: Build and Release
+section: Establish Baseline
+order: 16
+---
+# Exercise 3: Build and Release
+
+Before proceeding, we must ensure everything builds and release the new versions. We will use the provided helper scripts.
+
+Verify the changes with a build:
+
+```bash
+$WORKSHOP/build.sh
+```
+
+If successful, commit the changes:
+
+```bash
+mod git add $WORKSPACE --last-recipe-run
+mod git commit $WORKSPACE -m "Workshop baseline: Maven, Java 8, Spring Boot 2.7" --last-recipe-run
+```
+
+Now, perform a release to update dependencies and bump versions for the next step:
+
+```bash
+$WORKSHOP/release.sh
+```
+
+Finally, rebuild the LSTs so the CLI sees the updated code state:
+
+```bash
+mod build $WORKSPACE
+```
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/04-04-baseline-quiz.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/04-04-baseline-quiz.mdx
new file mode 100644
index 0000000..5821ad1
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/04-04-baseline-quiz.mdx
@@ -0,0 +1,15 @@
+---
+title: Baseline Quiz
+section: Establish Baseline
+order: 17
+---
+# Quiz
+
+Why do we establish a baseline before upgrading to Spring Boot 4?
+
+1. To make the migration harder.
+2. To ensure all repositories are on a known, stable configuration (e.g., Spring Boot 2.7) before attempting major upgrades.
+3. To confuse the developers.
+4. To test the CI pipeline.
+
+**Correct Answer**: 2
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/05-01-upgrade-java-17.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/05-01-upgrade-java-17.mdx
new file mode 100644
index 0000000..c22859b
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/05-01-upgrade-java-17.mdx
@@ -0,0 +1,36 @@
+---
+title: Upgrade to Java 17
+section: Smoke Test
+order: 18
+---
+# Exercise 1: Upgrade to Java 17
+
+In this module, you will raise the Java baseline to 17 and run a controlled Spring Boot 4 upgrade to identify any remaining blockers.
+
+Spring Boot 3+ (and 4) requires Java 17 or higher. So, let's upgrade all repositories to Java 17 first.
+
+Run the `UpgradeToJava17` recipe:
+
+```bash
+mod run $WORKSPACE --recipe org.openrewrite.java.migrate.UpgradeToJava17
+```
+
+Apply the changes:
+
+```bash
+mod git apply $WORKSPACE --last-recipe-run
+```
+
+Now, build and commit the changes:
+
+```bash
+$WORKSHOP/build.sh
+mod git add $WORKSPACE --last-recipe-run
+mod git commit $WORKSPACE -m "Upgrade to Java 17" --last-recipe-run
+```
+
+And rebuild the LSTs:
+
+```bash
+mod build $WORKSPACE
+```
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/05-02-smoke-test.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/05-02-smoke-test.mdx
new file mode 100644
index 0000000..bc00b3c
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/05-02-smoke-test.mdx
@@ -0,0 +1,20 @@
+---
+title: Spring Boot 4 Smoke Test
+section: Smoke Test
+order: 19
+---
+# Exercise 2: Run a Spring Boot 4 Smoke Test
+
+Now that we are on Java 17, let's see how far the Spring Boot 4 upgrade gets. This is a "smoke test" to identify remaining blockers.
+
+Run the upgrade recipe:
+
+```bash
+mod run $WORKSPACE --recipe io.moderne.java.spring.boot4.UpgradeSpringBoot_4_0
+```
+
+Apply the changes temporarily:
+
+```bash
+mod git apply $WORKSPACE --last-recipe-run
+```
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/05-03-verify-waves.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/05-03-verify-waves.mdx
new file mode 100644
index 0000000..3214ac4
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/05-03-verify-waves.mdx
@@ -0,0 +1,20 @@
+---
+title: Verify Waves
+section: Smoke Test
+order: 20
+---
+# Exercise 3: Build Waves (Expect Failure)
+
+Let's try to build Wave 0 and Wave 1.
+
+```bash
+$WORKSHOP/build.sh 0
+```
+
+Wave 0 (common libraries) should build successfully.
+
+```bash
+$WORKSHOP/build.sh 1
+```
+
+Wave 1 is expected to fail. Why? Because of QueryDSL issues! We'll fix this in the next module.
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/05-04-restore-workspace.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/05-04-restore-workspace.mdx
new file mode 100644
index 0000000..4d5a344
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/05-04-restore-workspace.mdx
@@ -0,0 +1,24 @@
+---
+title: Restore Workspace
+section: Smoke Test
+order: 21
+---
+# Exercise 4: Restore Workspace
+
+Since the build is broken, let's revert the changes.
+
+```bash
+mod exec $WORKSPACE git restore .
+```
+
+Verify the workspace is clean:
+
+```bash
+mod git status $WORKSPACE
+```
+
+Rebuild LSTs to ensure we are back to the Java 17 baseline:
+
+```bash
+mod build $WORKSPACE
+```
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/05-05-smoke-test-quiz.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/05-05-smoke-test-quiz.mdx
new file mode 100644
index 0000000..fe65e26
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/05-05-smoke-test-quiz.mdx
@@ -0,0 +1,15 @@
+---
+title: Smoke Test Quiz
+section: Smoke Test
+order: 22
+---
+# Quiz
+
+Why do we run a smoke test before the full migration?
+
+1. To see if the computer is smoking.
+2. To commit broken code.
+3. To isolate remaining obstacles (like QueryDSL) without committing to a full upgrade.
+4. To verify that Java 17 is installed correctly.
+
+**Correct Answer**: 3
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-01-install-querydsl-recipe.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-01-install-querydsl-recipe.mdx
new file mode 100644
index 0000000..24fade7
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-01-install-querydsl-recipe.mdx
@@ -0,0 +1,25 @@
+---
+title: Install QueryDSL Recipe
+section: Finish Migration
+order: 23
+---
+# Exercise 1: Install the QueryDSL Recipe
+
+In this module, you will install a custom recipe to handle the QueryDSL issues, build a composite upgrade recipe, and upgrade each wave of repositories to Spring Boot 4.
+
+We need a custom recipe to rename packages and update dependencies for QueryDSL.
+
+Clone the custom recipe repository:
+
+```bash
+cd $PROJECTS
+git clone https://github.com/modernetraining/rewrite-querydsl.git
+```
+
+Build and install it locally:
+
+```bash
+cd rewrite-querydsl
+mvn clean install
+mod config recipes jar install org.openrewrite.recipe:rewrite-querydsl:0.1.0-SNAPSHOT
+```
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-02-create-composite-recipe.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-02-create-composite-recipe.mdx
new file mode 100644
index 0000000..b486af7
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-02-create-composite-recipe.mdx
@@ -0,0 +1,36 @@
+---
+title: Create Composite Recipe
+section: Finish Migration
+order: 24
+---
+# Exercise 2: Create a Composite Upgrade Recipe
+
+Create a composite recipe that combines:
+* `UpgradeDependencyVersion` (to bump internal dependencies).
+* `UpgradeSpringBoot_4_0` (Spring Boot 4).
+* `UpgradeToQueryDsl5` (QueryDSL 5).
+
+Create a file named `CustomUpgradeSpringBoot_4_0.yml` in your `$WORKSHOP` directory:
+
+```bash
+cat <<'EOF' > $WORKSHOP/CustomUpgradeSpringBoot_4_0.yml
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: org.openrewrite.recipe.querydsl.CustomUpgradeSpringBoot_4_0
+displayName: CustomUpgradeSpringBoot_4_0
+description: Upgrade internal deps, Spring Boot 4.0, and QueryDSL 5.
+recipeList:
+ - org.openrewrite.java.dependencies.UpgradeDependencyVersion:
+ groupId: com.example.ecom
+ artifactId: "*"
+ newVersion: 1.x
+ - io.moderne.java.spring.boot4.UpgradeSpringBoot_4_0
+ - org.openrewrite.recipe.querydsl.UpgradeToQueryDsl5
+EOF
+```
+
+Install the composite recipe locally:
+
+```bash
+mod config recipes yaml install $WORKSHOP/CustomUpgradeSpringBoot_4_0.yml
+```
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-03-upgrade-wave-0.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-03-upgrade-wave-0.mdx
new file mode 100644
index 0000000..a985711
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-03-upgrade-wave-0.mdx
@@ -0,0 +1,25 @@
+---
+title: Upgrade Wave 0
+section: Finish Migration
+order: 25
+---
+# Exercise 3: Upgrade Wave 0
+
+Run the custom recipe on Wave 0:
+
+```bash
+mod run $WORKSPACE/Wave0 --recipe org.openrewrite.recipe.querydsl.CustomUpgradeSpringBoot_4_0
+```
+
+Apply the changes:
+
+```bash
+mod git apply $WORKSPACE/Wave0 --last-recipe-run
+```
+
+Build and release Wave 0:
+
+```bash
+$WORKSHOP/build.sh 0
+$WORKSHOP/release.sh 0
+```
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-04-upgrade-wave-1.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-04-upgrade-wave-1.mdx
new file mode 100644
index 0000000..3a0cf41
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-04-upgrade-wave-1.mdx
@@ -0,0 +1,27 @@
+---
+title: Upgrade Wave 1
+section: Finish Migration
+order: 26
+---
+# Exercise 4: Upgrade Wave 1
+
+Now repeat for Wave 1:
+
+```bash
+mod run $WORKSPACE/Wave1 --recipe org.openrewrite.recipe.querydsl.CustomUpgradeSpringBoot_4_0
+```
+
+Apply changes:
+
+```bash
+mod git apply $WORKSPACE/Wave1 --last-recipe-run
+```
+
+Build and release Wave 1:
+
+```bash
+$WORKSHOP/build.sh 1
+$WORKSHOP/release.sh 1
+```
+
+You can continue this process for Wave 2 and Wave 3 to complete the migration across all repositories.
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-05-refresh-devcenter.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-05-refresh-devcenter.mdx
new file mode 100644
index 0000000..5c172e8
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-05-refresh-devcenter.mdx
@@ -0,0 +1,28 @@
+---
+title: Refresh DevCenter
+section: Finish Migration
+order: 27
+---
+# Exercise 5: Refresh DevCenter
+
+After upgrading waves, you can generate a DevCenter dashboard to visualize the progress.
+
+First, rebuild LSTs:
+
+```bash
+mod build $WORKSPACE
+```
+
+Run the `DevCenterStarter` recipe:
+
+```bash
+mod run $WORKSPACE --recipe io.moderne.devcenter.DevCenterStarter
+```
+
+Generate the DevCenter dashboard:
+
+```bash
+mod devcenter $WORKSPACE --last-recipe-run
+```
+
+This will produce an HTML file you can open to see the updated status of your repositories.
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-06-finish-migration-quiz.mdx b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-06-finish-migration-quiz.mdx
new file mode 100644
index 0000000..0557a0f
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/06-06-finish-migration-quiz.mdx
@@ -0,0 +1,15 @@
+---
+title: Finish Migration Quiz
+section: Finish Migration
+order: 28
+---
+# Quiz
+
+Why is it beneficial to upgrade in waves using a composite recipe?
+
+1. To ensure that internal dependencies are updated in the correct order and custom issues (like QueryDSL) are handled alongside framework upgrades.
+2. To make the migration take longer.
+3. To confuse the build system.
+4. To avoid using the CLI.
+
+**Correct Answer**: 1
diff --git a/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/lesson.yml b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/lesson.yml
new file mode 100644
index 0000000..df78e17
--- /dev/null
+++ b/examples/moderne-spring-boot-migration/lessons/spring-boot-migration/lesson.yml
@@ -0,0 +1,2 @@
+title: Spring Boot Migration
+description: A hands-on workshop to migrate Spring Boot applications using OpenRewrite and Moderne.
diff --git a/pom.xml b/pom.xml
index 26a085b..6ce3c23 100644
--- a/pom.xml
+++ b/pom.xml
@@ -135,10 +135,6 @@
-
- org.graalvm.buildtools
- native-maven-plugin
-
org.springframework.boot
spring-boot-maven-plugin
@@ -176,6 +172,9 @@
turtorial
+
+ --initialize-at-build-time=jtermios.linux.JTermiosImpl$Linux_C_lib_DirectMapping
+
diff --git a/src/main/java/com/snowfort/turtorial/controller/SpaController.java b/src/main/java/com/snowfort/turtorial/controller/SpaController.java
new file mode 100644
index 0000000..3b9dfec
--- /dev/null
+++ b/src/main/java/com/snowfort/turtorial/controller/SpaController.java
@@ -0,0 +1,13 @@
+package com.snowfort.turtorial.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@Controller
+public class SpaController {
+
+ @GetMapping("/lesson/{id}")
+ public String forwardLesson() {
+ return "forward:/index.html";
+ }
+}
diff --git a/src/main/resources/META-INF/native-image/com.snowfort/turtorial/jni-config.json b/src/main/resources/META-INF/native-image/com.snowfort/turtorial/jni-config.json
new file mode 100644
index 0000000..bd6c8de
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/com.snowfort/turtorial/jni-config.json
@@ -0,0 +1,50 @@
+[
+ {
+ "name": "jtermios.linux.JTermiosImpl$Linux_C_lib_DirectMapping",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "jtermios.linux.JTermiosImpl$Linux_C_lib$termios",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "jtermios.linux.JTermiosImpl$Linux_C_lib$serial_struct",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "jtermios.linux.JTermiosImpl$Linux_C_lib$timeval",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "jtermios.linux.JTermiosImpl$Linux_C_lib$pollfd",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "com.sun.jna.Pointer",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "com.sun.jna.NativeLong",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "com.sun.jna.ptr.IntByReference",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ }
+]
diff --git a/src/main/resources/META-INF/native-image/com.snowfort/turtorial/proxy-config.json b/src/main/resources/META-INF/native-image/com.snowfort/turtorial/proxy-config.json
new file mode 100644
index 0000000..faa9c49
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/com.snowfort/turtorial/proxy-config.json
@@ -0,0 +1,11 @@
+[
+ {
+ "interfaces": ["com.pty4j.unix.linux.OSFacadeImpl$C_lib"]
+ },
+ {
+ "interfaces": ["com.pty4j.unix.linux.OSFacadeImpl$Linux_Util_lib"]
+ },
+ {
+ "interfaces": ["jtermios.linux.JTermiosImpl$Linux_C_lib"]
+ }
+]
diff --git a/src/main/resources/META-INF/native-image/com.snowfort/turtorial/reflect-config.json b/src/main/resources/META-INF/native-image/com.snowfort/turtorial/reflect-config.json
new file mode 100644
index 0000000..684b232
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/com.snowfort/turtorial/reflect-config.json
@@ -0,0 +1,119 @@
+[
+ {
+ "name": "jtermios.linux.JTermiosImpl$Linux_C_lib_DirectMapping",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "jtermios.linux.JTermiosImpl$Linux_C_lib",
+ "allDeclaredMethods": true
+ },
+ {
+ "name": "jtermios.linux.JTermiosImpl$Linux_C_lib$termios",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "jtermios.linux.JTermiosImpl$Linux_C_lib$serial_struct",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "jtermios.linux.JTermiosImpl$Linux_C_lib$timeval",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "jtermios.linux.JTermiosImpl$Linux_C_lib$pollfd",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "jtermios.linux.JTermiosImpl$FDSetImpl",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "jtermios.linux.JTermiosImpl",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "jtermios.JTermios",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "com.pty4j.unix.linux.OSFacadeImpl",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "com.pty4j.unix.linux.OSFacadeImpl$C_lib",
+ "allDeclaredMethods": true
+ },
+ {
+ "name": "com.pty4j.unix.linux.OSFacadeImpl$Linux_Util_lib",
+ "allDeclaredMethods": true
+ },
+ {
+ "name": "com.pty4j.unix.PtyHelpers",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "com.pty4j.unix.PtyHelpers$OSFacade",
+ "allDeclaredMethods": true
+ },
+ {
+ "name": "com.pty4j.unix.Pty",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "com.pty4j.unix.UnixPtyProcess",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "com.pty4j.PtyProcessBuilder",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true
+ },
+ {
+ "name": "com.sun.jna.Structure",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "com.sun.jna.Pointer",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "com.sun.jna.NativeLong",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ },
+ {
+ "name": "com.sun.jna.ptr.IntByReference",
+ "allDeclaredConstructors": true,
+ "allDeclaredMethods": true,
+ "allDeclaredFields": true
+ }
+]
diff --git a/src/main/resources/META-INF/native-image/com.snowfort/turtorial/resource-config.json b/src/main/resources/META-INF/native-image/com.snowfort/turtorial/resource-config.json
new file mode 100644
index 0000000..223b077
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/com.snowfort/turtorial/resource-config.json
@@ -0,0 +1,13 @@
+{
+ "resources": {
+ "includes": [
+ {"pattern": "resources/com/pty4j/native/.*"},
+ {"pattern": "com/sun/jna/.*"},
+ {"pattern": "lessons/.*"},
+ {"pattern": "schemas/.*"},
+ {"pattern": "static/.*"},
+ {"pattern": ".*\\.yml"},
+ {"pattern": ".*\\.yaml"}
+ ]
+ }
+}