Files
spring-boot-demo/custom-validation
Claude e4b5636f7c Add custom-validation, etag-caching, restclient-basic-auth: Boot 4.1 API pass
Three companion modules verifying and rewriting the Boot 4.1.1 / Framework
7.0.9 story for three older articles: the javax->jakarta.validation namespace
fix plus Jakarta Validation 3.1 record-validation clarification, ETag/
conditional-request APIs re-verified unchanged plus the starter rename, and
RestTemplate Basic Auth rebuilt on RestClient with the exchange() trap called
out. 19 real passing tests generate every transcript quoted from the three
companion articles.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EQNA6DJ9VgCtW6zhCE8Xud
2026-09-19 10:17:09 +00:00
..

custom-validation

Companion module for Custom Validation in Spring Boot: Beyond the Basics! on ankurm.com, rewritten around Jakarta Validation 3.1 (Spring Boot 4.1.1 / Hibernate Validator 9.1.3.Final) and the jakarta.validation namespace the original article's javax.validation code never actually ran under on Boot 3+.

mvn test regenerates every transcript in docs/output/ -- the test suite is the transcript generator.

Versions

Spring Boot 4.1.1
Spring Framework 7.0.9
Hibernate Validator 9.1.3.Final (jakarta.validation-api 3.1.1) -- confirmed with mvn dependency:tree and by reading the jars' own manifests, see docs/01
JDK Eclipse Temurin 25.0.4.1 (LTS)

Quickstart

export JAVA_HOME=/path/to/jdk-25
mvn -DskipTests package
./scripts/run.sh                 # port 8080
curl -s -X POST localhost:8080/contact -H 'Content-Type: application/json' \
     -d '{"email":"[email protected]","message":"hello there, this message is long enough"}'
mvn test                         # 10 tests, regenerates docs/output/

Endpoints

Endpoint Shows
POST /contact field-level custom constraint (@SpamMessageCheck), class-based DTO, BindingResult
POST /contact-record the same constraint on a record component
POST /booking class-level (cross-field) custom constraint (@DateRangeValid), class-based DTO
POST /booking-record the same cross-field constraint on a record's type declaration
GET /diagnostic/validation-provider prints the real Bean Validation provider and spec version at runtime -- delete before shipping

Documentation

  1. The jakarta.validation namespace, and what Bean Validation 3.1 actually changed
  2. Record validation, field-level and class-level
  3. What the defaults do not do: record validation failures with no error body

Findings worth the trip

  • The original article's javax.validation.* imports never worked on Spring Boot 3+. Boot 3 moved entirely to the jakarta.* namespace in December 2022; this module is the corrected, runnable version.
  • Spring Boot 4.1.1 pins Hibernate Validator 9.1.3.Final / Jakarta Validation 3.1, confirmed by mvn dependency:tree and independently by reading the actual jar manifests at test time.
  • A record validation failure with no BindingResult in the controller signature returns an EMPTY 400 body by default -- not a JSON error, not even with Accept: application/json.
  • spring.mvc.problemdetails.enabled=true fixes the empty body but not the missing detail: the resulting ProblemDetail is generic ("Invalid request content."), with no per-field messages, unless you write a custom @ExceptionHandler to put them there yourself.
  • AutoConfigureMockMvc moved packages in Boot 4.1, from org.springframework.boot.test.autoconfigure.web.servlet to org.springframework.boot.webmvc.test.autoconfigure -- and spring-boot-starter-test alone no longer pulls in MockMvc's autoconfiguration; that needs spring-boot-starter-webmvc-test.

License

MIT -- see LICENSE.