Skip to content

Disable lenient parsing in DateFormType#4175

Open
gilisd wants to merge 3 commits into
flowable:mainfrom
gilisd:fix/date-form-type-lenient-parsing
Open

Disable lenient parsing in DateFormType#4175
gilisd wants to merge 3 commits into
flowable:mainfrom
gilisd:fix/date-form-type-lenient-parsing

Conversation

@gilisd

@gilisd gilisd commented Mar 4, 2026

Copy link
Copy Markdown

FastDateFormat.parseObject() silently accepted invalid dates by rolling over values (e.g. month 13 became month 1 of the next year). Use SimpleDateFormat with setLenient(false) to reject invalid dates.

Check List:

  • Unit tests: YES
  • Documentation: NA

FastDateFormat.parseObject() silently accepted invalid dates by rolling
over values (e.g. month 13 became month 1 of the next year).
Use SimpleDateFormat with setLenient(false) to reject invalid dates.
@jbarrez

jbarrez commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Thanks @gilisd,

Good catch.

Looking at the existing code, there is now the dateFormat field which gets initialized in the constructor. However, it's now only used in the other method. Isn't there anything in the FastDateFormat class that can be used to reuse the field?

FastDateFormat does not support lenient=false. Using local SimpleDateFormat
instances in both methods avoids shared state and is thread-safe. The overhead
of creating a new instance per call is acceptable as these methods are not
expected to be called frequently on the same object.
@gilisd

gilisd commented Mar 18, 2026

Copy link
Copy Markdown
Author

Thanks @gilisd,

Good catch.

Looking at the existing code, there is now the dateFormat field which gets initialized in the constructor. However, it's now only used in the other method. Isn't there anything in the FastDateFormat class that can be used to reuse the field?

Good point. FastDateFormat does not support setLenient(false) — it is explicitly lenient by default, so it cannot be used for strict parsing.

We addressed your concern by removing the dateFormat field entirely. Both methods now use a local SimpleDateFormat instance, which keeps the code consistent and avoids shared state.

Thread-safety is preserved since local variables are per-thread. The overhead of creating a new instance per call is acceptable as these methods are not expected to be called frequently on the same object.

@filiphr

filiphr commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

@gilisd in order to avoid the creation of the date pattern on every conversion, can't we create 2 fields? Thread safety is fine since we are not going to change those 2 fields at any time.

@filiphr

filiphr commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

@gilisd, thinking a bit more about this. This PR now fully disables lenient parsing and we switch the date format from FastDateFormat to SimpleDateFormat.

Looking at the Javadoc of FastDateFormat it says:

Javadoc cites for the year pattern: For formatting, if the number of pattern letters is 2, the year is truncated to 2 digits; otherwise it is interpreted as a number. Starting with Java 1.7 a pattern of 'Y' or 'YYY' will be formatted as '2003', while it was '03' in former Java versions. FastDateFormat implements the behavior of Java 7.

Perhaps we need to keep the FastDateFormat for the formatting, otherwise it could be a behaviour change. Additionally, we perhaps need to offer lenientDateFormat or lenientDateFormatParse as a boolean flag to FormProperty and DateFormType in order to allow for backwards compatibility in case someone relied on the lenient parsing.

Date form properties now parse strictly by default, rejecting rolled-over
dates (e.g. 15/13/2024) and trailing characters. The previous lenient
behaviour can be restored per engine (ProcessEngineConfiguration) or per
form property (lenientDateParsing attribute), with the property-level
flag taking precedence. Formatting still uses FastDateFormat, so the
output format is unchanged.
@DavidGilis

Copy link
Copy Markdown

@jbarrez @filiphr Thanks for the review. I've reworked the PR based on your comments — a summary of what changed and why:

FastDateFormat field restored — formatting unchanged. convertModelValueToFormValue and the lenient parse path use the shared FastDateFormat field again, so there is no behaviour change in formatting. Side note on the Y-pattern concern: that javadoc passage describes the Java ≤6 vs 7+ difference, and on the JDK 17 baseline both implementations produce identical output for the patterns I tested. Keeping FastDateFormat for formatting still seems the safer choice, so I did.

Why the strict parse path has no second field. Strict parsing now uses DateUtils.parseDateStrictly(value, datePattern), which besides disabling leniency also rejects trailing characters — a plain non-lenient SimpleDateFormat still accepted "15/06/2024abc", since parse() only matches a prefix. On the two-fields suggestion: form types registered on the engine (the default "date" type and anything in customFormTypes) are engine-wide singletons used concurrently, and SimpleDateFormat is unsafe under concurrent use even when the field reference never changes — its internal Calendar mutates during parse. The per-datePattern instances are in any case rebuilt on every form command by FormHandlerHelper, so a parse-side field would save one pattern compilation per submit. A local parse seemed the better trade-off.

Backwards compatibility: lenientDateParsing on two levels. As suggested, there is now an opt-out — as an engine-wide default (ProcessEngineConfiguration.setLenientDateParsing) and as a per-property BPMN attribute (<flowable:formProperty ... lenientDateParsing="true"/>), with the property-level value taking precedence (Boolean: absent = follow engine default). That gives installations a single switch to restore the old behaviour, and per-definition granularity — which also covers multi-tenant setups, since deployments are tenant-scoped. I kept strict as the default because lenient rollover silently stores corrupted dates: the problem then surfaces far downstream as wrong data to troubleshoot, instead of a parse error at the boundary. I picked the name lenientDateParsing over lenientDateFormatParse since it names the behaviour rather than the implementing class — I can rename if you prefer your original suggestion.

Known limitations, intentionally left as-is: the property-level flag is only honoured when a datePattern is set (a date property without one resolves to the engine-registered shared type); strict mode also rejects trailing text and case-variant month names that the old FastDateFormat parser tolerated; and the flowable5 compatibility engine keeps its own (lenient) DateFormType.

@filiphr filiphr 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.

Thanks @DavidGilis. This looks OK to me. I'll let the CI run and then we can merge it

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.

4 participants