Skip to content

WW-5816 Add a range validator for java.time.LocalDate - #2060

Merged
lukaszlenart merged 3 commits into
mainfrom
WW-5816-localdate-range-validator
Oct 25, 2026
Merged

lukaszlenart merged 3 commits into
mainfrom
WW-5816-localdate-range-validator

Conversation

@lukaszlenart

Copy link
Copy Markdown
Member

Fixes WW-5816

Problem

DateRangeFieldValidator (date) is a RangeValidatorSupport<Date>. On a java.time.LocalDate property it compares the value against java.util.Date bounds and throws ClassCastException, so a LocalDate field has no working range validation. Conversion and the date tags have supported LocalDate for a while; the validators had not.

Change

  • Validator: LocalDateRangeFieldValidator extends RangeValidatorSupport<LocalDate>, registered as localdate in the default validators.
    • Bounds are min/max as ISO dates, or minExpression/maxExpression.
    • Both bounds are inclusive, and a null value is skipped (left to a required validator), all inherited from RangeValidatorSupport.
  • XML params: an XML <param name="min">2026-01-01</param> reaches the generic setMin(T) as a LocalDate through the usual parameter conversion. That was the open question in the ticket, and a test pins it.
  • Annotation: @LocalDateRangeFieldValidator mirrors @DateRangeFieldValidator without dateFormat, since LocalDate bounds are ISO only. It is processed on a method and inside @Validations via the new localDateRangeFields() member, whose empty default keeps existing code compiling.
  • Malformed bounds: the builder parses annotation bounds with LocalDate.parse, so a malformed bound fails when the validators are built instead of passing through as a string.
  • Unchanged: DateRangeFieldValidator. LocalDateTime/LocalTime can follow on demand.

Tests

  • LocalDateRangeFieldValidatorTest:
    • inside the range, on both bounds, below and above the range, null value skipped, bounds from expressions
    • the localdate registration
    • ISO string params converted to LocalDate when the validator is built from configuration
  • AnnotationValidationConfigurationBuilderTest:
    • method-level and @Validations annotations produce localdate validators with LocalDate bounds
    • a non-ISO bound fails with DateTimeParseException
  • Full core suite on JDK 17 and JDK 25, plus the spring, junit, convention and json plugins on JDK 17, all green.

Follow-ups

  • struts-site validator reference for localdate.
  • WW-5811 maps this validator's bounds to HTML5 min/max on date controls.

🤖 Generated with Claude Code

DateRangeFieldValidator compares against java.util.Date bounds, so on a
LocalDate property it throws ClassCastException. LocalDateRangeFieldValidator
is a RangeValidatorSupport<LocalDate>, registered as "localdate", with
ISO min/max bounds (or minExpression/maxExpression), both inclusive.

An XML min such as "2026-01-01" reaches the generic setMin(T) as a
LocalDate through the usual parameter conversion, which a test pins.

@LocalDateRangeFieldValidator mirrors @DateRangeFieldValidator without
dateFormat, since the bounds are ISO only. It is processed on a method
and inside @validations via the new localDateRangeFields() member. The
builder parses the bounds with LocalDate.parse, so a malformed bound
fails when the validators are built instead of passing through as a
string.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

XML bounds can be interpreted using the action's global date format rather than ISO, producing incorrect validation limits.

1 open issue
What changed in this PR

Adds localdate range validation to Struts core for java.time.LocalDate properties, reusing the existing range-validation infrastructure without changing the legacy date validator.

Changes:

  • Registers a LocalDate validator supporting inclusive bounds and bound expressions.
  • Adds method-level annotation support and integration with @Validations.
  • Adds tests for range behavior, registration, configuration conversion, and annotation parsing.
File Description
core/​src/​test/​java/​org/​apache/​struts2/​validator/​validators/​LocalDateRangeFieldValidatorTest.java Tests range behavior, expressions, registration, and bound conversion.
core/​src/​test/​java/​org/​apache/​struts2/​validator/​AnnotationValidationConfigurationBuilderTest.java Tests annotation processing and malformed-bound rejection.
core/​src/​test/​java/​org/​apache/​struts2/​validator/​AnnotationMalformedLocalDateRangeAction.java Provides an invalid-bound annotation fixture.
core/​src/​test/​java/​org/​apache/​struts2/​validator/​AnnotationLocalDateRangeAction.java Provides standalone and grouped annotation fixtures.
core/​src/​main/​resources/​org/​apache/​struts2/​validator/​validators/​default.xml Registers the localdate validator.
core/​src/​main/​java/​org/​apache/​struts2/​validator/​validators/​LocalDateRangeFieldValidator.java Implements and documents the LocalDate range validator.
core/​src/​main/​java/​org/​apache/​struts2/​validator/​AnnotationValidationConfigurationBuilder.java Builds LocalDate validator configurations from annotations.
core/​src/​main/​java/​org/​apache/​struts2/​validator/​annotations/​Validations.java Adds the LocalDate annotation collection.
core/​src/​main/​java/​org/​apache/​struts2/​validator/​annotations/​LocalDateRangeFieldValidator.java Defines the new validation annotation.

Review effort: Balanced

lukaszlenart and others added 2 commits October 25, 2026 10:05
…ates

XML min/max params reached setMin/setMax through DateConverter, which
tries the application's struts.date.format before ISO: with yyyy-dd-MM,
a min of 2026-01-02 became February 1. The annotation bounds were
already ISO-only.

LocalDateRangeFieldValidator-conversion.properties maps min and max to
LocalDateRangeBoundConverter, which parses them with LocalDate.parse, so
XML bounds no longer depend on the display format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The converter's error branch is unreachable through buildValidator,
which swallows conversion errors on validator params (WW-5818/WW-5819),
so it was uncovered. Test the converter on its own: an ISO bound is
parsed (trimmed), a non-ISO bound throws TypeConversionException naming
the param with the DateTimeParseException as cause, and an already
converted LocalDate passes through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
7.1% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@lukaszlenart
lukaszlenart merged commit ce27f88 into main Oct 25, 2026
7 of 8 checks passed
@lukaszlenart
lukaszlenart deleted the WW-5816-localdate-range-validator branch October 25, 2026 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants