Add boot42-rc1-checklist module: Spring Boot 4.2.0-RC1 upgrade checklist run against RC1 and 4.1.2

This commit is contained in:
2026-10-22 18:34:34 +00:00
parent 2996628ff1
commit 917d39fb65
25 changed files with 631 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
# boot42-rc1-checklist
Companion module for the ankurm.com article *Spring Framework 7.1 and Spring Boot 4.2: What's Coming and How to Prepare*.
Everything runs against **Spring Boot 4.2.0-RC1** (Spring Framework 7.1.0-RC1; a release candidate, **not GA**) and, for the before/after comparison, against **4.1.2** (GA). JDK 25.
| Path | What it is |
|---|---|
| `pom.xml` / `pom-41.xml` | the same project on 4.2.0-RC1 and on 4.1.2 (a `${revision}` parent does not resolve from a repository, hence two files) |
| `src/test/.../DefaultValuePlaceholderTest` | `@DefaultValue` with a `${...}` placeholder: resolved on 4.2, literal text on 4.1.2 (and a startup failure for an `int`) |
| `src/test/.../KafkaAdminBindingTest` | the new `spring.kafka.template.admin.*` / `listener.admin.*` keys: 5 of 5 bind on 4.2, 0 of 5 on 4.1.2 with no error |
| `src/test/.../LdapEmbeddedSslTest` | embedded LDAP `ssl.enabled=true` without a bundle: fails on 4.2, starts on 4.1.2 |
| `src/test/.../MigratorTest` | what `spring-boot-properties-migrator` prints for the relocated Tomcat keys and the removed path-matching key |
| `src/broken/UsesRestTemplate.java` | not built; `scripts/compile-broken.sh` compiles it with `-Xlint:deprecation` and keeps the compiler's own message |
| `output/` | every transcript quoted in the article; `./scripts/run-all.sh` regenerates them |
Run: `./scripts/run-all.sh`. Not executed here (they need Docker or a registry): the new default buildpack builder and the image-based build cache.
There is deliberately no `docs/` folder: the explanations live in the article.
@@ -0,0 +1,6 @@
# 01-default-value-placeholders (Spring Boot 4.1.2, JDK 25)
no demo.* properties set: zone = ${demo.region:eu}-primary plain = static-value
demo.region=ap: zone = ${demo.region:eu}-primary
int property, placeholder default: context FAILED to start
root cause: java.lang.NumberFormatException
For input string: "${demoport.number:8080}"
@@ -0,0 +1,4 @@
# 01-default-value-placeholders (Spring Boot 4.2.0-RC1, JDK 25)
no demo.* properties set: zone = eu-primary plain = static-value
demo.region=ap: zone = ap-primary
int property, placeholder default: number = 8080
@@ -0,0 +1,10 @@
# 02-kafka-admin-binding (Spring Boot 4.1.2, JDK 25)
properties supplied: 5
IGNORED spring.kafka.listener.admin.client-id
IGNORED spring.kafka.listener.admin.fail-fast
IGNORED spring.kafka.listener.await-async-results-on-stop
IGNORED spring.kafka.template.admin.client-id
IGNORED spring.kafka.template.admin.fail-fast
bound = 0 of 5
listener.isAwaitAsyncResultsOnStop() = no such method
template.getAdmin() = no such method
@@ -0,0 +1,14 @@
# 02-kafka-admin-binding (Spring Boot 4.2.0-RC1, JDK 25)
properties supplied: 5
bound spring.kafka.listener.admin.client-id
bound spring.kafka.listener.admin.fail-fast
bound spring.kafka.listener.await-async-results-on-stop
bound spring.kafka.template.admin.client-id
bound spring.kafka.template.admin.fail-fast
bound = 5 of 5
listener.isAwaitAsyncResultsOnStop() = true
template.getAdmin() = org.springframework.boot.kafka.autoconfigure.KafkaProperties$SimpleAdmin@889d9e8
template admin client-id = template-admin
template admin fail-fast = true
listener admin client-id = listener-admin
listener admin fail-fast = true
@@ -0,0 +1,3 @@
# 03-ldap-embedded-ssl-without-bundle (Spring Boot 4.1.2, JDK 25)
spring.ldap.embedded.ssl.enabled=true, no bundle: context STARTED
embedded server beans: 1
@@ -0,0 +1,4 @@
# 03-ldap-embedded-ssl-without-bundle (Spring Boot 4.2.0-RC1, JDK 25)
spring.ldap.embedded.ssl.enabled=true, no bundle: context FAILED to start
java.lang.IllegalStateException
SSL is enabled but no SSL bundle has been set. An SSL bundle providing the server's certificate and private key is required for LDAPS
@@ -0,0 +1 @@
# 04-properties-migrator-report (Spring Boot 4.1.2, JDK 25)
@@ -0,0 +1,13 @@
# 04-properties-migrator-report (Spring Boot 4.2.0-RC1, JDK 25)
The use of configuration keys that have been renamed was found in the environment:
Property source 'defaultProperties':
Key: server.tomcat.additional-tld-skip-patterns
Replacement: server.tomcat.servlet.additional-tld-skip-patterns
Key: server.tomcat.redirect-context-root
Replacement: server.tomcat.servlet.redirect-context-root
Key: server.tomcat.use-relative-redirects
Replacement: server.tomcat.servlet.use-relative-redirects
The use of configuration keys that are no longer supported was found in the environment:
Property source 'defaultProperties':
Key: spring.mvc.pathmatch.matching-strategy
Reason: Path matching is no longer configurable
@@ -0,0 +1,3 @@
# 05-resttemplate-compiler-warnings (Spring Boot 4.1.2, JDK 25.0.5)
$ javac -Xlint:deprecation -cp <module classpath> -d target/broken src/broken/UsesRestTemplate.java
exit: 0
@@ -0,0 +1,10 @@
# 05-resttemplate-compiler-warnings (Spring Boot 4.2.0-RC1, JDK 25.0.5)
$ javac -Xlint:deprecation -cp <module classpath> -d target/broken src/broken/UsesRestTemplate.java
src/broken/UsesRestTemplate.java:11: warning: [removal] RestTemplateBuilder in org.springframework.boot.restclient has been deprecated and marked for removal
public String fetch(RestTemplateBuilder builder) {
^
src/broken/UsesRestTemplate.java:12: warning: [removal] RestTemplate in org.springframework.web.client has been deprecated and marked for removal
RestTemplate rest = builder.build();
^
2 warnings
exit: 0
+74
View File
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.2</version>
<relativePath/>
</parent>
<groupId>com.ankurm</groupId>
<artifactId>boot42-rc1-checklist</artifactId>
<version>1.0.0</version>
<name>boot42-rc1-checklist</name>
<description>Spring Boot 4.2.0-RC1 upgrade checklist: each claim run against 4.2.0-RC1 and 4.1.2</description>
<properties>
<java.version>25</java.version>
<boot.label>4.1.2</boot.label>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-ldap</artifactId>
</dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<boot.label>${boot.label}</boot.label>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
+74
View File
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.2.0-RC1</version>
<relativePath/>
</parent>
<groupId>com.ankurm</groupId>
<artifactId>boot42-rc1-checklist</artifactId>
<version>1.0.0</version>
<name>boot42-rc1-checklist</name>
<description>Spring Boot 4.2.0-RC1 upgrade checklist: each claim run against 4.2.0-RC1 and 4.1.2</description>
<properties>
<java.version>25</java.version>
<boot.label>4.2.0-RC1</boot.label>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-ldap</artifactId>
</dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<boot.label>${boot.label}</boot.label>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Compiles src/broken/UsesRestTemplate.java with -Xlint:deprecation against the module classpath
# of the given pom (default pom.xml = 4.2.0-RC1; pass pom-41.xml for the 4.1.2 baseline) and
# stores the compiler's own message in output/05-resttemplate-compiler-warnings[-on-4.1.2].txt.
set -u
cd "$(dirname "$0")/.."
POM="${1:-pom.xml}"
LABEL=$(grep -m1 '<boot.label>' "$POM" | sed -E 's/.*<boot.label>(.*)<\/boot.label>.*/\1/')
SUFFIX=""; [[ "$LABEL" == 4.2* ]] || SUFFIX="-on-$LABEL"
OUT="output/05-resttemplate-compiler-warnings$SUFFIX.txt"
mvn -B -q -f "$POM" dependency:build-classpath -Dmdep.outputFile=target/cp.txt >/dev/null 2>&1
rm -rf target/broken && mkdir -p target/broken
{
echo "# 05-resttemplate-compiler-warnings (Spring Boot $LABEL, JDK $(javac -version 2>&1 | grep -v Picked | awk '{print $2}'))"
echo '$ javac -Xlint:deprecation -cp <module classpath> -d target/broken src/broken/UsesRestTemplate.java'
javac -Xlint:deprecation -cp "$(cat target/cp.txt)" -d target/broken src/broken/UsesRestTemplate.java 2>&1 \
| grep -v '^Picked up' | sed -e "s#^$(pwd)/##"
echo "exit: ${PIPESTATUS[0]}"
} > "$OUT"
cat "$OUT"
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Regenerates every file in output/ on Spring Boot 4.2.0-RC1 and on the 4.1.2 baseline.
# Needs JDK 25 and Maven 3.9 on PATH.
set -euo pipefail
cd "$(dirname "$0")/.."
mvn -B -q test
mvn -B -q -f pom-41.xml test
scripts/compile-broken.sh pom.xml >/dev/null
scripts/compile-broken.sh pom-41.xml >/dev/null
echo "done: $(ls output | wc -l) files in output/"
@@ -0,0 +1,15 @@
import org.springframework.web.client.RestTemplate;
import org.springframework.boot.restclient.RestTemplateBuilder;
/**
* Not part of the build. scripts/compile-broken.sh compiles it with -Xlint:deprecation and
* commits the compiler's own message. RestTemplate and the Boot builder around it are
* deprecated in Spring Framework 7.1 / Spring Boot 4.2.
*/
public class UsesRestTemplate {
public String fetch(RestTemplateBuilder builder) {
RestTemplate rest = builder.build();
return rest.getForObject("http://localhost:8080/ping", String.class);
}
}
@@ -0,0 +1,12 @@
package com.ankurm.rc1;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
/**
* A non-String property with a placeholder default. On 4.1.x the literal text
* {@code ${demo.port:8080}} cannot be converted to int, so the context fails to start.
*/
@ConfigurationProperties("demoport")
public record DemoPort(@DefaultValue("${demoport.number:8080}") int number) {
}
@@ -0,0 +1,16 @@
package com.ankurm.rc1;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
/**
* Constructor-bound properties whose defaults contain placeholders.
*
* <p>Spring Boot 4.2.0-M2 made {@code @DefaultValue} resolve property placeholders during
* binding. On 4.1.x the same annotation value is taken literally.
*/
@ConfigurationProperties("demo")
public record DemoProperties(
@DefaultValue("${demo.region:eu}-primary") String zone,
@DefaultValue("static-value") String plain) {
}
@@ -0,0 +1,14 @@
package com.ankurm.rc1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
@SpringBootApplication
@ConfigurationPropertiesScan
public class Rc1ChecklistApplication {
public static void main(String[] args) {
SpringApplication.run(Rc1ChecklistApplication.class, args);
}
}
@@ -0,0 +1,68 @@
package com.ankurm.rc1;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
class DefaultValuePlaceholderTest {
@EnableConfigurationProperties(DemoProperties.class)
static class StringConfig {
}
@EnableConfigurationProperties(DemoPort.class)
static class PortConfig {
}
@Test
void placeholderInDefaultValue() {
Transcript t = new Transcript("01-default-value-placeholders");
new ApplicationContextRunner().withUserConfiguration(StringConfig.class).run(ctx -> {
DemoProperties p = ctx.getBean(DemoProperties.class);
t.line("no demo.* properties set: zone = " + p.zone() + " plain = " + p.plain());
if (Transcript.is42()) {
assertThat(p.zone()).isEqualTo("eu-primary");
}
else {
assertThat(p.zone()).isEqualTo("${demo.region:eu}-primary");
}
});
new ApplicationContextRunner().withUserConfiguration(StringConfig.class)
.withPropertyValues("demo.region=ap")
.run(ctx -> {
DemoProperties p = ctx.getBean(DemoProperties.class);
t.line("demo.region=ap: zone = " + p.zone());
if (Transcript.is42()) {
assertThat(p.zone()).isEqualTo("ap-primary");
}
else {
assertThat(p.zone()).isEqualTo("${demo.region:eu}-primary");
}
});
new ApplicationContextRunner().withUserConfiguration(PortConfig.class).run(ctx -> {
if (Transcript.is42()) {
assertThat(ctx).hasNotFailed();
t.line("int property, placeholder default: number = " + ctx.getBean(DemoPort.class).number());
assertThat(ctx.getBean(DemoPort.class).number()).isEqualTo(8080);
}
else {
assertThat(ctx).hasFailed();
Throwable root = rootCause(ctx.getStartupFailure());
t.line("int property, placeholder default: context FAILED to start");
t.line("root cause: " + root.getClass().getName());
t.line(" " + root.getMessage());
}
});
t.write();
}
private static Throwable rootCause(Throwable ex) {
Throwable t = ex;
while (t.getCause() != null) {
t = t.getCause();
}
return t;
}
}
@@ -0,0 +1,98 @@
package com.ankurm.rc1;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.kafka.autoconfigure.KafkaProperties;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Binds the new Kafka admin properties and records which of the five keys the binder actually
* consumed. On 4.2 all five bind. On 4.1.2 Boot does not complain about the unknown keys; they
* are simply never read, which is the failure mode worth knowing about.
*/
class KafkaAdminBindingTest {
private static final Map<String, String> PROPS = Map.of(
"spring.kafka.template.admin.client-id", "template-admin",
"spring.kafka.template.admin.fail-fast", "true",
"spring.kafka.listener.admin.client-id", "listener-admin",
"spring.kafka.listener.admin.fail-fast", "true",
"spring.kafka.listener.await-async-results-on-stop", "true");
@Test
void adminPropertiesBind() throws Exception {
Transcript t = new Transcript("02-kafka-admin-binding");
Set<String> consumed = new TreeSet<>();
BindHandler recorder = new BindHandler() {
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context,
Object result) {
if (context.getConfigurationProperty() != null) {
consumed.add(name.toString());
}
return result;
}
};
Binder binder = new Binder(new MapConfigurationPropertySource(PROPS));
KafkaProperties kp = binder.bind("spring.kafka", Bindable.of(KafkaProperties.class), recorder)
.orElseGet(KafkaProperties::new);
t.line("properties supplied: " + PROPS.size());
for (String key : new TreeSet<>(PROPS.keySet())) {
t.line((consumed.contains(key.substring("spring.kafka.".length())) || consumed.contains(key) ? "bound " : "IGNORED ")
+ key);
}
long bound = PROPS.keySet().stream()
.filter(k -> consumed.contains(k.substring("spring.kafka.".length())) || consumed.contains(k))
.count();
t.line("bound = " + bound + " of " + PROPS.size());
Object listener = call(kp, "getListener");
Object template = call(kp, "getTemplate");
t.line("listener.isAwaitAsyncResultsOnStop() = " + callOrAbsent(listener, "isAwaitAsyncResultsOnStop"));
t.line("template.getAdmin() = " + callOrAbsent(template, "getAdmin"));
if (Transcript.is42()) {
assertThat(bound).isEqualTo(5);
Object tplAdmin = call(template, "getAdmin");
Object lsnAdmin = call(listener, "getAdmin");
t.line("template admin client-id = " + call(tplAdmin, "getClientId"));
t.line("template admin fail-fast = " + call(tplAdmin, "isFailFast"));
t.line("listener admin client-id = " + call(lsnAdmin, "getClientId"));
t.line("listener admin fail-fast = " + call(lsnAdmin, "isFailFast"));
assertThat(call(tplAdmin, "getClientId")).isEqualTo("template-admin");
assertThat(call(lsnAdmin, "isFailFast")).isEqualTo(true);
}
else {
assertThat(bound).isZero();
}
t.write();
}
private static Object call(Object target, String method) throws Exception {
Method m = target.getClass().getMethod(method);
return m.invoke(target);
}
private static String callOrAbsent(Object target, String method) {
try {
return String.valueOf(call(target, method));
}
catch (NoSuchMethodException ex) {
return "no such method";
}
catch (Exception ex) {
return ex.toString();
}
}
}
@@ -0,0 +1,44 @@
package com.ankurm.rc1;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.ldap.autoconfigure.embedded.EmbeddedLdapAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* 4.2.0-M2: spring.ldap.embedded.ssl.enabled=true without spring.ldap.embedded.ssl.bundle now
* fails at startup instead of quietly starting a plain-text listener.
*/
class LdapEmbeddedSslTest {
@Test
void sslWithoutBundle() {
Transcript t = new Transcript("03-ldap-embedded-ssl-without-bundle");
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(EmbeddedLdapAutoConfiguration.class))
.withPropertyValues("spring.ldap.embedded.base-dn=dc=example,dc=org",
"spring.ldap.embedded.port=0",
"spring.ldap.embedded.ssl.enabled=true")
.run(ctx -> {
if (Transcript.is42()) {
assertThat(ctx).hasFailed();
Throwable root = ctx.getStartupFailure();
while (root.getCause() != null) {
root = root.getCause();
}
t.line("spring.ldap.embedded.ssl.enabled=true, no bundle: context FAILED to start");
t.line(root.getClass().getName());
t.line(root.getMessage());
}
else {
assertThat(ctx).hasNotFailed();
t.line("spring.ldap.embedded.ssl.enabled=true, no bundle: context STARTED");
t.line("embedded server beans: "
+ ctx.getBeanNamesForType(com.unboundid.ldap.listener.InMemoryDirectoryServer.class).length);
}
});
t.write();
}
}
@@ -0,0 +1,54 @@
package com.ankurm.rc1;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Starts an empty application with the property keys 4.2 relocated or deprecated and captures
* what spring-boot-properties-migrator prints. The keys come from the 4.2.0-M2 configuration
* changelog.
*/
@ExtendWith(OutputCaptureExtension.class)
class MigratorTest {
@Configuration(proxyBeanMethods = false)
static class Empty {
}
@Test
void migratorReport(CapturedOutput output) {
Transcript t = new Transcript("04-properties-migrator-report");
SpringApplication app = new SpringApplication(Empty.class);
app.setWebApplicationType(WebApplicationType.NONE);
app.setDefaultProperties(java.util.Map.of(
"server.tomcat.use-relative-redirects", "false",
"server.tomcat.redirect-context-root", "false",
"server.tomcat.additional-tld-skip-patterns", "*.jar",
"spring.mvc.pathmatch.matching-strategy", "path_pattern_parser",
"management.metrics.observations.ignored-meters", "http.server.requests"));
app.run().close();
boolean any = false;
for (String line : output.getAll().split("\n")) {
String s = line.strip();
if (s.startsWith("Property source") || s.startsWith("Key:") || s.startsWith("Reason:")
|| s.startsWith("Replacement:") || s.contains("configuration keys")
|| s.contains("have been renamed") || s.contains("deprecated") && s.contains("configuration")
|| s.contains("no longer supported") || s.contains("been removed")) {
t.line(s);
any = true;
}
}
if (Transcript.is42()) {
assertThat(output.getAll()).contains("server.tomcat.servlet.use-relative-redirects");
assertThat(any).isTrue();
}
t.write();
}
}
@@ -0,0 +1,45 @@
package com.ankurm.rc1;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
/**
* Collects lines and writes output/NN-name.txt (or NN-name-on-4.1.2.txt when run against the
* 4.1 baseline). The test asserts the same values it writes, so every figure quoted in the
* article is an assertion that fails the build when it stops being true.
*/
final class Transcript {
static final String LABEL = System.getProperty("boot.label", "4.2.0-RC1");
static boolean is42() {
return LABEL.startsWith("4.2");
}
private final List<String> lines = new ArrayList<>();
private final Path file;
Transcript(String name) {
String suffix = is42() ? "" : "-on-" + LABEL;
this.file = Path.of("output", name + suffix + ".txt");
line("# " + name + " (Spring Boot " + LABEL + ", JDK " + Runtime.version().feature() + ")");
}
Transcript line(String s) {
lines.add(s);
return this;
}
void write() {
try {
Files.createDirectories(file.getParent());
Files.write(file, lines);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}