OpenRewrite demo — Spring Boot 2 to 4, Java 17 to 25, JUnit 4 to 5

Recipes run by OpenRewrite against the demo project's actual source — every line below is a real change the recipes made, not a mock-up. Generated 2026-08-14 09:47 UTC.

Before → after

Spring Boot
2.7.184.0.x
Java
1725
Test framework
JUnit 4JUnit 5
Files changed
8 files, +212 -140

Recipes applied

Tests

Before migration: 18/18 passed
After migration: 17/18 passed, 1 failing

One pre-existing test intentionally asserted the OLD buggy behaviour (a NullPointerException on a null role) — EqualsAvoidsNull just fixed that bug, so the test now fails and needs updating. That's expected: automated codemods still need a human to review tests that encoded the bug they fix.

Files changed (8)

OpenRewrite/Dockerfile+2 -2
@@ -11,7 +11,7 @@
# bash scripts/run-image.sh # run on http://localhost:8080
# ─────────────────────────────────────────────────────────────────────────────
# ── Stage 1: Build ────────────────────────────────────────────────────────────
-FROM eclipse-temurin:21-jdk-alpine AS builder
+FROM eclipse-temurin:25-jdk-alpine AS builder
WORKDIR /workspace
# Copy Maven wrapper and pom first so dependency layer is cached
COPY .mvn/ .mvn/
@@ -23,7 +23,7 @@ RUN ./mvnw -B dependency:go-offline -q
COPY src/ src/
RUN ./mvnw -B clean package -DskipTests -q
# ── Stage 2: Runtime ──────────────────────────────────────────────────────────
-FROM eclipse-temurin:21-jre-alpine
+FROM eclipse-temurin:25-jre-alpine
LABEL org.opencontainers.image.title="openrewrite-demo"
LABEL org.opencontainers.image.description="OpenRewrite migration demo — Spring Boot + JUnit 4 before rewrite"
LABEL org.opencontainers.image.base.name="eclipse-temurin:21-jre-alpine"
OpenRewrite/pom.xml+23 -20
@@ -27,7 +27,7 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!-- ⬇ Intentionally old — OpenRewrite will upgrade this to 4.0.x -->
- <version>2.7.18</version>
+ <version>4.0.7</version>
<relativePath/>
</parent>
@@ -39,13 +39,13 @@
<properties>
<!-- ⬇ Intentionally Java 17 — UpgradeToJava25 recipe bumps this to 25 -->
- <java.version>17</java.version>
+ <java.version>25</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
+ <artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
@@ -53,23 +53,6 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
-
- <!--
- JUnit 4 is kept intentionally so the demo starts with old-style tests.
- run-openrewrite.sh migrates them to JUnit 5 (Jupiter).
- junit-vintage-engine lets them run before the migration.
- -->
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.13.2</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.junit.vintage</groupId>
- <artifactId>junit-vintage-engine</artifactId>
- <scope>test</scope>
- </dependency>
</dependencies>
<build>
@@ -78,6 +61,26 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>get-mockito-agent-path</id>
+ <goals>
+ <goal>properties</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <!--suppress MavenModelInspection -->
+ <argLine>-javaagent:${net.bytebuddy:byte-buddy-agent:jar}</argLine>
+ </configuration>
+ </plugin>
</plugins>
</build>
OpenRewrite/src/main/java/com/example/openrewrite/OpenRewriteDemoApplication.java+5 -1
@@ -1,10 +1,14 @@
package com.example.openrewrite;
+
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+
// ── AutoFormat ──────────────────────────────────────────────────────────────
// The main method body and brace placement below is intentionally compact /
// poorly formatted. AutoFormat will expand it to the standard Java style.
@SpringBootApplication
public class OpenRewriteDemoApplication {
-public static void main(String[] args){SpringApplication.run(OpenRewriteDemoApplication.class,args);}
+ public static void main(String[] args) {
+ SpringApplication.run(OpenRewriteDemoApplication.class, args);
+ }
}
OpenRewrite/src/main/java/com/example/openrewrite/controller/GreetingController.java+24 -18
@@ -1,8 +1,10 @@
package com.example.openrewrite.controller;
+
import com.example.openrewrite.model.Person;
import com.example.openrewrite.service.GreetingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
+
/**
* REST controller — AutoFormat will fix missing spaces and inconsistent
* brace / indentation style throughout.
@@ -15,22 +17,26 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/api")
public class GreetingController {
// AutoFormat: @Autowired without surrounding whitespace
-@Autowired
-private GreetingService greetingService;
-@GetMapping("/greet")
-public String greet(@RequestParam(defaultValue="World") String name){
-return greetingService.greet(name);
-}
-@GetMapping("/welcome")
-public String welcomePage(@RequestParam String name){
-return greetingService.getWelcomePage(name);
-}
-@PostMapping("/access")
-public boolean checkAccess(@RequestBody Person person,@RequestParam String resource){
-return greetingService.canAccess(person,resource);
-}
-@GetMapping("/reserved")
-public boolean isReserved(@RequestParam String name){
-return greetingService.isReservedName(name);
-}
+ @Autowired
+ private GreetingService greetingService;
+
+ @GetMapping("/greet")
+ public String greet(@RequestParam(defaultValue = "World") String name) {
+ return greetingService.greet(name);
+ }
+
+ @GetMapping("/welcome")
+ public String welcomePage(@RequestParam String name) {
+ return greetingService.getWelcomePage(name);
+ }
+
+ @PostMapping("/access")
+ public boolean checkAccess(@RequestBody Person person, @RequestParam String resource) {
+ return greetingService.canAccess(person, resource);
+ }
+
+ @GetMapping("/reserved")
+ public boolean isReserved(@RequestParam String name) {
+ return greetingService.isReservedName(name);
+ }
}
OpenRewrite/src/main/java/com/example/openrewrite/model/Person.java+44 -27
@@ -1,4 +1,5 @@
package com.example.openrewrite.model;
+
/**
* Demo model class that intentionally contains code patterns that OpenRewrite will fix:
*
@@ -19,39 +20,55 @@ package com.example.openrewrite.model;
*/
// ── AutoFormat: compact constructor / missing spaces ──────────────────────
public class Person {
-private String name;
-private int age;
-private String role;
-public Person(String name,int age,String role){
-this.name=name;
-this.age=age;
-this.role=role;
-}
-public String getName(){return name;}
-public int getAge(){return age;}
-public String getRole(){return role;}
+ private String name;
+ private int age;
+ private String role;
+
+ public Person(String name, int age, String role) {
+ this.name = name;
+ this.age = age;
+ this.role = role;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public String getRole() {
+ return role;
+ }
+
// ── EqualsAvoidsNull ──────────────────────────────────────────────────────
// BEFORE: role.equals("admin") → NullPointerException when role is null
// AFTER: "admin".equals(role) → null-safe
-public boolean isAdmin(){
-return role.equals("admin");
-}
+ public boolean isAdmin() {
+ return "admin".equals(role);
+ }
+
// ── EqualsAvoidsNull (chained OR) ─────────────────────────────────────────
-public boolean isPrivileged(){
-return role.equals("admin")||role.equals("superuser");
-}
+ public boolean isPrivileged() {
+ return "admin".equals(role) || "superuser".equals(role);
+ }
+
// ── UpgradeToJava25: multi-line string concatenation → text block ─────────
// BEFORE: ugly string concatenation
// AFTER: clean text block with """
-public String describe(){
-String template = "Name : %s\n" +
-"Age : %d\n" +
-"Role : %s\n";
-return String.format(template, name, age, role);
-}
+ public String describe() {
+ String template = """
+ Name : %s
+ Age : %d
+ Role : %s
+ """;
+ return String.format(template, name, age, role);
+ }
+
// ── AutoFormat: inconsistent spacing around operators ─────────────────────
-@Override
-public String toString(){
-return "Person{name='"+name+"', age="+age+", role='"+role+"'}";
-}
+ @Override
+ public String toString() {
+ return "Person{name='" + name + "', age=" + age + ", role='" + role + "'}";
+ }
}
OpenRewrite/src/main/java/com/example/openrewrite/service/GreetingService.java+47 -30
@@ -1,8 +1,10 @@
package com.example.openrewrite.service;
+
import com.example.openrewrite.model.Person;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.ArrayList;
+
/**
* Business-logic service — intentional issues for each OpenRewrite recipe:
*
@@ -20,37 +22,52 @@ import java.util.ArrayList;
*/
@Service
public class GreetingService {
-private static final List<String> RESERVED_NAMES=new ArrayList<>();
-static{
-RESERVED_NAMES.add("admin");
-RESERVED_NAMES.add("root");
-RESERVED_NAMES.add("system");
-}
+ private static final List<String> RESERVED_NAMES = new ArrayList<>();
+
+ static{
+ RESERVED_NAMES.add("admin");
+ RESERVED_NAMES.add("root");
+ RESERVED_NAMES.add("system");
+ }
+
// ── EqualsAvoidsNull: name.equals("World") throws NPE when name is null ───
-public String greet(String name){
-if(name.equals("World")){return "Hello, World!";}
-if(name.equals("admin")){return "Hello, Administrator!";}
-return "Hello, "+name+"!";
-}
+ public String greet(String name) {
+ if("World".equals(name)) {
+ return "Hello, World!";
+ }
+ if("admin".equals(name)) {
+ return "Hello, Administrator!";
+ }
+ return "Hello, " + name + "!";
+ }
+
// ── UpgradeToJava25: multi-line concatenation → text block ────────────────
-public String getWelcomePage(String name){
-String template="<!DOCTYPE html>\n"+
-"<html>\n"+
-" <head><title>Welcome</title></head>\n"+
-" <body>\n"+
-" <h1>Welcome, %s!</h1>\n"+
-" <p>OpenRewrite demo application.</p>\n"+
-" </body>\n"+
-"</html>\n";
-return String.format(template,name);
-}
+ public String getWelcomePage(String name) {
+ String template = """
+ <!DOCTYPE html>
+ <html>
+ <head><title>Welcome</title></head>
+ <body>
+ <h1>Welcome, %s!</h1>
+ <p>OpenRewrite demo application.</p>
+ </body>
+ </html>
+ """;
+ return String.format(template, name);
+ }
+
// ── EqualsAvoidsNull: person.getRole().equals("admin") ────────────────────
-public boolean canAccess(Person person,String resource){
-if(person.getRole().equals("admin")){return true;}
-if(resource.equals("public")){return true;}
-return false;
-}
-public boolean isReservedName(String name){
-return RESERVED_NAMES.contains(name);
-}
+ public boolean canAccess(Person person, String resource) {
+ if("admin".equals(person.getRole())) {
+ return true;
+ }
+ if("public".equals(resource)) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isReservedName(String name) {
+ return RESERVED_NAMES.contains(name);
+ }
}
OpenRewrite/src/test/java/com/example/openrewrite/GreetingServiceTest.java+27 -19
@@ -1,14 +1,14 @@
package com.example.openrewrite;
+
import com.example.openrewrite.model.Person;
import com.example.openrewrite.service.GreetingService;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.After;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
+
/**
* JUnit 4 integration test — JUnit4to5Migration recipe rewrites this to JUnit 5:
*
@@ -23,58 +23,66 @@ import org.springframework.test.context.junit4.SpringRunner;
* org.junit.Assert.assertFalse(…) Assertions.assertFalse(…)
* org.junit.Assert.assertNotNull(…) Assertions.assertNotNull(…)
*/
-@RunWith(SpringRunner.class)
@SpringBootTest
public class GreetingServiceTest {
@Autowired
private GreetingService greetingService;
private String testName;
private Person testPerson;
+
// ── JUnit4to5Migration: @Before → @BeforeEach ─────────────────────────
- @Before
+ @BeforeEach
public void setUp() {
testName = "Alice";
testPerson = new Person("Alice", 30, "user");
}
+
// ── JUnit4to5Migration: @After → @AfterEach ───────────────────────────
- @After
+ @AfterEach
public void tearDown() {
testName = null;
testPerson = null;
}
+
// ── JUnit4to5Migration: Assert.assertEquals → Assertions.assertEquals ─
@Test
public void testGreetWorld() {
String result = greetingService.greet("World");
- Assert.assertNotNull(result);
- Assert.assertEquals("Hello, World!", result);
+ Assertions.assertNotNull(result);
+ Assertions.assertEquals("Hello, World!", result);
}
+
@Test
public void testGreetWithName() {
String result = greetingService.greet(testName);
- Assert.assertNotNull(result);
- Assert.assertTrue(result.contains(testName));
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.contains(testName));
}
+
@Test
public void testGreetAdmin() {
- Assert.assertEquals("Hello, Administrator!", greetingService.greet("admin"));
+ Assertions.assertEquals("Hello, Administrator!", greetingService.greet("admin"));
}
+
@Test
public void testCanAccessAsAdmin() {
Person admin = new Person("Bob", 25, "admin");
- Assert.assertTrue(greetingService.canAccess(admin, "secret"));
+ Assertions.assertTrue(greetingService.canAccess(admin, "secret"));
}
+
@Test
public void testCanAccessPublicResource() {
- Assert.assertTrue(greetingService.canAccess(testPerson, "public"));
+ Assertions.assertTrue(greetingService.canAccess(testPerson, "public"));
}
+
@Test
public void testCannotAccessPrivateResource() {
- Assert.assertFalse(greetingService.canAccess(testPerson, "secret"));
+ Assertions.assertFalse(greetingService.canAccess(testPerson, "secret"));
}
+
@Test
public void testIsReservedName() {
- Assert.assertTrue(greetingService.isReservedName("admin"));
- Assert.assertFalse(greetingService.isReservedName(testName));
+ Assertions.assertTrue(greetingService.isReservedName("admin"));
+ Assertions.assertFalse(greetingService.isReservedName(testName));
}
}
OpenRewrite/src/test/java/com/example/openrewrite/PersonTest.java+40 -23
@@ -1,8 +1,12 @@
package com.example.openrewrite;
+
import com.example.openrewrite.model.Person;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
/**
* Pure JUnit 4 unit test (no Spring context) — JUnit4to5Migration recipe
* rewrites this class to use JUnit 5 annotations and assertion methods.
@@ -14,65 +18,78 @@ public class PersonTest {
private Person person;
private Person adminPerson;
private Person nullRolePerson;
- @Before
+
+ @BeforeEach
public void setUp() {
- person = new Person("Alice", 30, "user");
- adminPerson = new Person("Bob", 25, "admin");
+ person = new Person("Alice", 30, "user");
+ adminPerson = new Person("Bob", 25, "admin");
// EqualsAvoidsNull demo: nullRolePerson.isAdmin() used in testIsAdminWithNullRole
nullRolePerson = new Person("Charlie", 20, null);
}
+
@Test
public void testGetName() {
- Assert.assertEquals("Alice", person.getName());
+ Assertions.assertEquals("Alice", person.getName());
}
+
@Test
public void testGetAge() {
- Assert.assertEquals(30, person.getAge());
+ Assertions.assertEquals(30, person.getAge());
}
+
@Test
public void testGetRole() {
- Assert.assertEquals("user", person.getRole());
+ Assertions.assertEquals("user", person.getRole());
}
+
@Test
public void testIsAdminFalse() {
- Assert.assertFalse(person.isAdmin());
+ Assertions.assertFalse(person.isAdmin());
}
+
@Test
public void testIsAdminTrue() {
- Assert.assertTrue(adminPerson.isAdmin());
+ Assertions.assertTrue(adminPerson.isAdmin());
}
+
// ── @Test(expected) → assertThrows (JUnit5) ───────────────────────────
// BEFORE migration: isAdmin() on a null role throws NPE because it calls
// role.equals("admin") — EqualsAvoidsNull fixes this too.
- @Test(expected = NullPointerException.class)
+ @Test
public void testIsAdminWithNullRoleThrowsNPE() {
- // This test documents the BUG: after EqualsAvoidsNull migration,
- // "admin".equals(null) returns false instead of throwing.
- nullRolePerson.isAdmin();
+ assertThrows(NullPointerException.class, () ->
+ // This test documents the BUG: after EqualsAvoidsNull migration,
+ // "admin".equals(null) returns false instead of throwing.
+ nullRolePerson.isAdmin());
}
+
@Test
public void testIsPrivilegedAdmin() {
- Assert.assertTrue(adminPerson.isPrivileged());
+ Assertions.assertTrue(adminPerson.isPrivileged());
}
+
@Test
public void testIsPrivilegedUser() {
- Assert.assertFalse(person.isPrivileged());
+ Assertions.assertFalse(person.isPrivileged());
}
+
@Test
public void testDescribeContainsName() {
String desc = person.describe();
- Assert.assertNotNull(desc);
- Assert.assertTrue(desc.contains("Alice"));
+ Assertions.assertNotNull(desc);
+ Assertions.assertTrue(desc.contains("Alice"));
}
+
@Test
public void testDescribeContainsAge() {
- Assert.assertTrue(person.describe().contains("30"));
+ Assertions.assertTrue(person.describe().contains("30"));
}
+
@Test
public void testToString() {
String str = person.toString();
- Assert.assertNotNull(str);
- Assert.assertTrue(str.contains("Alice"));
- Assert.assertTrue(str.contains("user"));
+ Assertions.assertNotNull(str);
+ Assertions.assertTrue(str.contains("Alice"));
+ Assertions.assertTrue(str.contains("user"));
}
}