From 917d39fb65e7e3c712ebea7601f9368995a5ce26 Mon Sep 17 00:00:00 2001 From: Ankur Mhatre Date: Thu, 22 Oct 2026 18:34:18 +0000 Subject: [PATCH] Add boot42-rc1-checklist module: Spring Boot 4.2.0-RC1 upgrade checklist run against RC1 and 4.1.2 --- README.md | 1 + boot42-rc1-checklist/README.md | 18 ++++ ...01-default-value-placeholders-on-4.1.2.txt | 6 ++ .../output/01-default-value-placeholders.txt | 4 + .../02-kafka-admin-binding-on-4.1.2.txt | 10 ++ .../output/02-kafka-admin-binding.txt | 14 +++ ...p-embedded-ssl-without-bundle-on-4.1.2.txt | 3 + .../03-ldap-embedded-ssl-without-bundle.txt | 4 + ...04-properties-migrator-report-on-4.1.2.txt | 1 + .../output/04-properties-migrator-report.txt | 13 +++ ...esttemplate-compiler-warnings-on-4.1.2.txt | 3 + .../05-resttemplate-compiler-warnings.txt | 10 ++ boot42-rc1-checklist/pom-41.xml | 74 ++++++++++++++ boot42-rc1-checklist/pom.xml | 74 ++++++++++++++ .../scripts/compile-broken.sh | 20 ++++ boot42-rc1-checklist/scripts/run-all.sh | 10 ++ .../src/broken/UsesRestTemplate.java | 15 +++ .../main/java/com/ankurm/rc1/DemoPort.java | 12 +++ .../java/com/ankurm/rc1/DemoProperties.java | 16 +++ .../ankurm/rc1/Rc1ChecklistApplication.java | 14 +++ .../rc1/DefaultValuePlaceholderTest.java | 68 +++++++++++++ .../com/ankurm/rc1/KafkaAdminBindingTest.java | 98 +++++++++++++++++++ .../com/ankurm/rc1/LdapEmbeddedSslTest.java | 44 +++++++++ .../java/com/ankurm/rc1/MigratorTest.java | 54 ++++++++++ .../test/java/com/ankurm/rc1/Transcript.java | 45 +++++++++ 25 files changed, 631 insertions(+) create mode 100644 boot42-rc1-checklist/README.md create mode 100644 boot42-rc1-checklist/output/01-default-value-placeholders-on-4.1.2.txt create mode 100644 boot42-rc1-checklist/output/01-default-value-placeholders.txt create mode 100644 boot42-rc1-checklist/output/02-kafka-admin-binding-on-4.1.2.txt create mode 100644 boot42-rc1-checklist/output/02-kafka-admin-binding.txt create mode 100644 boot42-rc1-checklist/output/03-ldap-embedded-ssl-without-bundle-on-4.1.2.txt create mode 100644 boot42-rc1-checklist/output/03-ldap-embedded-ssl-without-bundle.txt create mode 100644 boot42-rc1-checklist/output/04-properties-migrator-report-on-4.1.2.txt create mode 100644 boot42-rc1-checklist/output/04-properties-migrator-report.txt create mode 100644 boot42-rc1-checklist/output/05-resttemplate-compiler-warnings-on-4.1.2.txt create mode 100644 boot42-rc1-checklist/output/05-resttemplate-compiler-warnings.txt create mode 100644 boot42-rc1-checklist/pom-41.xml create mode 100644 boot42-rc1-checklist/pom.xml create mode 100755 boot42-rc1-checklist/scripts/compile-broken.sh create mode 100755 boot42-rc1-checklist/scripts/run-all.sh create mode 100644 boot42-rc1-checklist/src/broken/UsesRestTemplate.java create mode 100644 boot42-rc1-checklist/src/main/java/com/ankurm/rc1/DemoPort.java create mode 100644 boot42-rc1-checklist/src/main/java/com/ankurm/rc1/DemoProperties.java create mode 100644 boot42-rc1-checklist/src/main/java/com/ankurm/rc1/Rc1ChecklistApplication.java create mode 100644 boot42-rc1-checklist/src/test/java/com/ankurm/rc1/DefaultValuePlaceholderTest.java create mode 100644 boot42-rc1-checklist/src/test/java/com/ankurm/rc1/KafkaAdminBindingTest.java create mode 100644 boot42-rc1-checklist/src/test/java/com/ankurm/rc1/LdapEmbeddedSslTest.java create mode 100644 boot42-rc1-checklist/src/test/java/com/ankurm/rc1/MigratorTest.java create mode 100644 boot42-rc1-checklist/src/test/java/com/ankurm/rc1/Transcript.java diff --git a/README.md b/README.md index 74ccd35..80d7b64 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ files. | [`file-transfer/`](file-transfer) | [File Upload and Download in Spring Boot 4: Multipart, Streaming and S3 Presigned URLs](https://ankurm.com/file-upload-download-spring-boot-4-multipart-streaming-s3-presigned-urls/) | multipart limits enforced by Tomcat (and ignored by `MockMvc`), the client-controlled filename, `Range` downloads, a 200 MB download and 150 MB upload under `-Xmx64m`, and S3 presigned URLs checked against a local SigV4 verifier (not a real S3) | | [`project-structure/`](project-structure) | [Spring Boot Project Structure: Package-by-Layer vs Package-by-Feature](https://ankurm.com/spring-boot-project-structure/) | the same application written package-by-layer and package-by-feature with one HTTP API, the real source trees, 9 of 9 types public by layer vs 3 of 15 by feature, and the `javac` error that makes a feature boundary a compiler rule instead of a convention | | [`crud-mysql/`](crud-mysql) | [Spring Boot CRUD REST API with MySQL and Spring Data JPA](https://ankurm.com/spring-boot-crud-rest-api-mysql/) | a complete book API on a real MySQL 8.4.6 server with Flyway owning the schema and Hibernate set to `validate`, `ProblemDetail` errors with per-field validation messages, a duplicate ISBN caught by the database's unique key and mapped to `409` on create and on update, MySQL's own `Duplicate entry` message, and the SQL behind every request | +| [`boot42-rc1-checklist/`](boot42-rc1-checklist) | [Spring Framework 7.1 and Spring Boot 4.2: What's Coming and How to Prepare](https://ankurm.com/spring-boot-4-2-spring-framework-7-1-upgrade-checklist/) | each 4.2 milestone change run on 4.2.0-RC1 and on 4.1.2: `@DefaultValue` placeholders resolved, five new Kafka admin keys that 4.1.2 silently ignores, embedded LDAP SSL failing without a bundle, the properties migrator's real report (and the removed key it misses), and the `RestTemplate` removal warnings from the compiler | `core-di`, `core-beans`, `custom-starter`, `core-events`, `multi-datasource`, `i18n`, `aot-cache`, `kotlin`, `testing-no-containers`, `rest-test-client` and `hexagonal` are the exception: they have **no `docs/NN-topic.md` chapter folder**. Their deeper material lives in collapsible sections inside the articles themselves. Where their captured output sits is not diff --git a/boot42-rc1-checklist/README.md b/boot42-rc1-checklist/README.md new file mode 100644 index 0000000..d81e0f7 --- /dev/null +++ b/boot42-rc1-checklist/README.md @@ -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. diff --git a/boot42-rc1-checklist/output/01-default-value-placeholders-on-4.1.2.txt b/boot42-rc1-checklist/output/01-default-value-placeholders-on-4.1.2.txt new file mode 100644 index 0000000..b81d54b --- /dev/null +++ b/boot42-rc1-checklist/output/01-default-value-placeholders-on-4.1.2.txt @@ -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}" diff --git a/boot42-rc1-checklist/output/01-default-value-placeholders.txt b/boot42-rc1-checklist/output/01-default-value-placeholders.txt new file mode 100644 index 0000000..fa255d0 --- /dev/null +++ b/boot42-rc1-checklist/output/01-default-value-placeholders.txt @@ -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 diff --git a/boot42-rc1-checklist/output/02-kafka-admin-binding-on-4.1.2.txt b/boot42-rc1-checklist/output/02-kafka-admin-binding-on-4.1.2.txt new file mode 100644 index 0000000..c544ea2 --- /dev/null +++ b/boot42-rc1-checklist/output/02-kafka-admin-binding-on-4.1.2.txt @@ -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 diff --git a/boot42-rc1-checklist/output/02-kafka-admin-binding.txt b/boot42-rc1-checklist/output/02-kafka-admin-binding.txt new file mode 100644 index 0000000..244ac7c --- /dev/null +++ b/boot42-rc1-checklist/output/02-kafka-admin-binding.txt @@ -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 diff --git a/boot42-rc1-checklist/output/03-ldap-embedded-ssl-without-bundle-on-4.1.2.txt b/boot42-rc1-checklist/output/03-ldap-embedded-ssl-without-bundle-on-4.1.2.txt new file mode 100644 index 0000000..e4d6f13 --- /dev/null +++ b/boot42-rc1-checklist/output/03-ldap-embedded-ssl-without-bundle-on-4.1.2.txt @@ -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 diff --git a/boot42-rc1-checklist/output/03-ldap-embedded-ssl-without-bundle.txt b/boot42-rc1-checklist/output/03-ldap-embedded-ssl-without-bundle.txt new file mode 100644 index 0000000..b74f2f3 --- /dev/null +++ b/boot42-rc1-checklist/output/03-ldap-embedded-ssl-without-bundle.txt @@ -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 diff --git a/boot42-rc1-checklist/output/04-properties-migrator-report-on-4.1.2.txt b/boot42-rc1-checklist/output/04-properties-migrator-report-on-4.1.2.txt new file mode 100644 index 0000000..e7651e3 --- /dev/null +++ b/boot42-rc1-checklist/output/04-properties-migrator-report-on-4.1.2.txt @@ -0,0 +1 @@ +# 04-properties-migrator-report (Spring Boot 4.1.2, JDK 25) diff --git a/boot42-rc1-checklist/output/04-properties-migrator-report.txt b/boot42-rc1-checklist/output/04-properties-migrator-report.txt new file mode 100644 index 0000000..bd14200 --- /dev/null +++ b/boot42-rc1-checklist/output/04-properties-migrator-report.txt @@ -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 diff --git a/boot42-rc1-checklist/output/05-resttemplate-compiler-warnings-on-4.1.2.txt b/boot42-rc1-checklist/output/05-resttemplate-compiler-warnings-on-4.1.2.txt new file mode 100644 index 0000000..490bb61 --- /dev/null +++ b/boot42-rc1-checklist/output/05-resttemplate-compiler-warnings-on-4.1.2.txt @@ -0,0 +1,3 @@ +# 05-resttemplate-compiler-warnings (Spring Boot 4.1.2, JDK 25.0.5) +$ javac -Xlint:deprecation -cp -d target/broken src/broken/UsesRestTemplate.java +exit: 0 diff --git a/boot42-rc1-checklist/output/05-resttemplate-compiler-warnings.txt b/boot42-rc1-checklist/output/05-resttemplate-compiler-warnings.txt new file mode 100644 index 0000000..6593091 --- /dev/null +++ b/boot42-rc1-checklist/output/05-resttemplate-compiler-warnings.txt @@ -0,0 +1,10 @@ +# 05-resttemplate-compiler-warnings (Spring Boot 4.2.0-RC1, JDK 25.0.5) +$ javac -Xlint:deprecation -cp -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 diff --git a/boot42-rc1-checklist/pom-41.xml b/boot42-rc1-checklist/pom-41.xml new file mode 100644 index 0000000..8716a1c --- /dev/null +++ b/boot42-rc1-checklist/pom-41.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 4.1.2 + + + + com.ankurm + boot42-rc1-checklist + 1.0.0 + boot42-rc1-checklist + Spring Boot 4.2.0-RC1 upgrade checklist: each claim run against 4.2.0-RC1 and 4.1.2 + + + 25 + 4.1.2 + + + + + org.springframework.boot + spring-boot-starter-webmvc + + + org.springframework.boot + spring-boot-starter-restclient + + + org.springframework.boot + spring-boot-starter-kafka + + + org.springframework.boot + spring-boot-starter-ldap + + + com.unboundid + unboundid-ldapsdk + + + org.springframework.boot + spring-boot-properties-migrator + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + maven-surefire-plugin + + + ${boot.label} + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/boot42-rc1-checklist/pom.xml b/boot42-rc1-checklist/pom.xml new file mode 100644 index 0000000..6a84db4 --- /dev/null +++ b/boot42-rc1-checklist/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 4.2.0-RC1 + + + + com.ankurm + boot42-rc1-checklist + 1.0.0 + boot42-rc1-checklist + Spring Boot 4.2.0-RC1 upgrade checklist: each claim run against 4.2.0-RC1 and 4.1.2 + + + 25 + 4.2.0-RC1 + + + + + org.springframework.boot + spring-boot-starter-webmvc + + + org.springframework.boot + spring-boot-starter-restclient + + + org.springframework.boot + spring-boot-starter-kafka + + + org.springframework.boot + spring-boot-starter-ldap + + + com.unboundid + unboundid-ldapsdk + + + org.springframework.boot + spring-boot-properties-migrator + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + maven-surefire-plugin + + + ${boot.label} + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/boot42-rc1-checklist/scripts/compile-broken.sh b/boot42-rc1-checklist/scripts/compile-broken.sh new file mode 100755 index 0000000..7e9124a --- /dev/null +++ b/boot42-rc1-checklist/scripts/compile-broken.sh @@ -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 '' "$POM" | sed -E 's/.*(.*)<\/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 -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" diff --git a/boot42-rc1-checklist/scripts/run-all.sh b/boot42-rc1-checklist/scripts/run-all.sh new file mode 100755 index 0000000..1e3b7d1 --- /dev/null +++ b/boot42-rc1-checklist/scripts/run-all.sh @@ -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/" diff --git a/boot42-rc1-checklist/src/broken/UsesRestTemplate.java b/boot42-rc1-checklist/src/broken/UsesRestTemplate.java new file mode 100644 index 0000000..2cdf318 --- /dev/null +++ b/boot42-rc1-checklist/src/broken/UsesRestTemplate.java @@ -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); + } +} diff --git a/boot42-rc1-checklist/src/main/java/com/ankurm/rc1/DemoPort.java b/boot42-rc1-checklist/src/main/java/com/ankurm/rc1/DemoPort.java new file mode 100644 index 0000000..347c550 --- /dev/null +++ b/boot42-rc1-checklist/src/main/java/com/ankurm/rc1/DemoPort.java @@ -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) { +} diff --git a/boot42-rc1-checklist/src/main/java/com/ankurm/rc1/DemoProperties.java b/boot42-rc1-checklist/src/main/java/com/ankurm/rc1/DemoProperties.java new file mode 100644 index 0000000..4db5850 --- /dev/null +++ b/boot42-rc1-checklist/src/main/java/com/ankurm/rc1/DemoProperties.java @@ -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. + * + *

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) { +} diff --git a/boot42-rc1-checklist/src/main/java/com/ankurm/rc1/Rc1ChecklistApplication.java b/boot42-rc1-checklist/src/main/java/com/ankurm/rc1/Rc1ChecklistApplication.java new file mode 100644 index 0000000..379f2ab --- /dev/null +++ b/boot42-rc1-checklist/src/main/java/com/ankurm/rc1/Rc1ChecklistApplication.java @@ -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); + } +} diff --git a/boot42-rc1-checklist/src/test/java/com/ankurm/rc1/DefaultValuePlaceholderTest.java b/boot42-rc1-checklist/src/test/java/com/ankurm/rc1/DefaultValuePlaceholderTest.java new file mode 100644 index 0000000..3e46a00 --- /dev/null +++ b/boot42-rc1-checklist/src/test/java/com/ankurm/rc1/DefaultValuePlaceholderTest.java @@ -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; + } +} diff --git a/boot42-rc1-checklist/src/test/java/com/ankurm/rc1/KafkaAdminBindingTest.java b/boot42-rc1-checklist/src/test/java/com/ankurm/rc1/KafkaAdminBindingTest.java new file mode 100644 index 0000000..b767876 --- /dev/null +++ b/boot42-rc1-checklist/src/test/java/com/ankurm/rc1/KafkaAdminBindingTest.java @@ -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 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 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(); + } + } +} diff --git a/boot42-rc1-checklist/src/test/java/com/ankurm/rc1/LdapEmbeddedSslTest.java b/boot42-rc1-checklist/src/test/java/com/ankurm/rc1/LdapEmbeddedSslTest.java new file mode 100644 index 0000000..a6bbef7 --- /dev/null +++ b/boot42-rc1-checklist/src/test/java/com/ankurm/rc1/LdapEmbeddedSslTest.java @@ -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(); + } +} diff --git a/boot42-rc1-checklist/src/test/java/com/ankurm/rc1/MigratorTest.java b/boot42-rc1-checklist/src/test/java/com/ankurm/rc1/MigratorTest.java new file mode 100644 index 0000000..e9291c2 --- /dev/null +++ b/boot42-rc1-checklist/src/test/java/com/ankurm/rc1/MigratorTest.java @@ -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(); + } +} diff --git a/boot42-rc1-checklist/src/test/java/com/ankurm/rc1/Transcript.java b/boot42-rc1-checklist/src/test/java/com/ankurm/rc1/Transcript.java new file mode 100644 index 0000000..4a64f6b --- /dev/null +++ b/boot42-rc1-checklist/src/test/java/com/ankurm/rc1/Transcript.java @@ -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 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); + } + } +}