[
https://issues.apache.org/jira/browse/GROOVY-12152?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Paul King updated GROOVY-12152:
-------------------------------
Description:
h3. Summary
Assess and improve how the GDK handles {{java.util.Locale}}, following the
discussion on the GROOVY-12147 dev thread. The goal is *not* to remove
convenience methods, but to make each locale-sensitive method's contract
explicit, offer an explicit-{{Locale}} variant where one is missing, and fix a
couple of parse defaults before 6.0.0 ships. Almost all of this is
additive/non-breaking.
h3. Background
Two recent efforts touched this area:
* GROOVY-12147 added locale-aware number/currency/percent formatting and
parsing.
* GROOVY-12055 moved identifier/token case-folding to {{Locale.ROOT}}.
On the GROOVY-12147 thread it was noted that methods depending on the JVM
{{default}} locale are hard to test and surprising on servers/multi-locale
processes. A guiding principle we might want to land on (to match the JDK
stance):
* {{Locale.ROOT}} — data meant for machines: protocols, DB, file formats,
anything persisted or re-parsed.
* {{Locale.getDefault()}} — output meant for the human running the code (only a
valid proxy for "the user" in single-user/client contexts).
The catch is that {{Locale.getDefault()}} is only a good proxy for "the human"
when there is one human and they _are_ the process:
* On the *command line, a script, or a desktop app*, the JVM default genuinely
reflects the person reading the output — the default-locale convenience is
exactly right.
* On a *web server, batch job, or any multi-tenant process*, the JVM default is
_not_ the end user's locale — it is the container's ({{LANG}}, whatever ops
set, usually en-US or C/POSIX). The actual user is determined *per request*
(Accept-Language, a profile setting) and arrives as data, not as the process
default. There, a default-locale method silently formats for the wrong human,
and it looks correct in dev only because the dev box default happens to match
the test expectations.
This is also why default-locale methods are hard to test meaningfully (you have
to pin a locale, which defeats the "default"), and why the convenience must
always be paired with an explicit-{{Locale}} variant: the same call has a
different "correct" answer depending on where it runs, and only the caller
knows which deployment it is in.
The items below are potential work we might want to do to get closer to the
above guiding principle.
h3. Scope
All actionable items are in GDK extension classes ({{StringGroovyMethods}},
{{DefaultGroovyMethods}}, {{DateUtilExtensions}}/{{DateUtilStaticExtensions}},
{{DateTimeExtensions}}/{{DateTimeStaticExtensions}}).
h4. A. Case-insensitive / case-folding on human text (consistency + docs)
These take arbitrary user text, fold case, and are silent about their
(locale-independent) contract.
|| Method || Current || Action ||
| {{containsIgnoreCase}} | {{toLowerCase(Locale.ROOT)}} both sides | Align
mechanism with starts/ends; Javadoc "locale-independent" |
| {{startsWithIgnoreCase}}, {{endsWithIgnoreCase}} | JDK {{equalsIgnoreCase}}
(char-wise) | Same doc note; pick one shared mechanism |
| {{capitalize}}, {{uncapitalize}} | {{Character.to{Upper,Lower}Case}} (single
char) | Javadoc note only; low priority |
A {{Locale}} variant is generally *not* warranted here (search/compare wants
stability).
h4. B. Parsing / machine-ingestion
|| Method || Current default || Action ||
| {{CharSequence.toCurrencyNumber()}}, {{toPercentNumber()}} (no-arg) |
{{Locale.getDefault()}} | Change default to {{ROOT}} (or drop the no-arg)
*before 6.0.0*; keep the {{(Locale)}} variant; doc |
| {{CharSequence.toNumber(Locale)}} | {{Locale}}-only, no no-arg — _correct
as-is_ | Leave {{Locale}}-only. The locale-independent parse is already
provided by {{toBigDecimal()}}/{{toInteger()}}/{{as Number}} (Java-literal
grammar, not ROOT). A no-arg would either duplicate those or introduce
divergent {{NumberFormat}} semantics ({{"1,234"}} parses via ROOT
{{NumberFormat}} but throws via {{toBigDecimal}}). |
| {{Date.parse(format, input)}} / +TimeZone (static) | default locale
({{SimpleDateFormat}}) | Add {{(…, Locale)}} variant; doc |
| {{<java.time>.parse(text, pattern)}} ×9 types (static) | default locale
({{ofPattern}}) | Add {{(…, Locale)}} variant; doc |
h4. C. Human-facing formatting defaulting to the JVM default locale
Defensible defaults, but each needs (1) a JavaDoc line "uses the JVM's locale;
pass a {{Locale}} for server/per-user output" and (2) a {{Locale}} variant
where none exists. Or if the Locale variant explosion is not warranted, the
JavaDoc should point to the JDK long-hand for that case.
|| Method || Locale variant today? || Action ||
| {{Number.toCurrencyString()}} / {{toPercentString()}} (no-arg) | yes | Doc
the default; currency no-arg is a drop candidate (ROOT is meaningless for
currency) |
| {{Date.format(String)}}, {{format(String, TimeZone)}},
{{Calendar.format(String)}} | no | Add {{(…, Locale)}} variant + doc |
| {{Date.getDateString()}} / {{getTimeString()}} / {{getDateTimeString()}} | no
| Add a locale-aware variant (or doc as fixed default-locale) |
| {{<java.time>.format(String pattern)}} ×6 | no | Add {{(…, Locale)}} variant
+ doc |
| {{<java.time>.format(FormatStyle)}} ×6 | no | Add {{(…, Locale)}} variant +
doc |
| {{printf(...)}} ×4, {{sprintf(...)}} ×2 | no | Add a {{Locale}}-first
overload (mirrors {{String.format(Locale, …)}}); doc |
h4. Cross-cutting: Javadoc {{@see}} routing
Wire bidirectional {{@see}} links so each method routes the reader to the right
sibling: the locale-aware variant, the locale-free variant, and the raw JDK API
({{NumberFormat.getNumberInstance(Locale)}},
{{DateTimeFormatter.ofPattern(pattern, locale)}}, {{new
SimpleDateFormat(pattern, locale)}}). This makes the "no no-arg is intentional"
decisions self-documenting and keeps the JDK escape hatch one hop away.
h3. Out of scope
The GROOVY-12055 identifier/token case-folding (class/property/CLI/SQL names,
etc., mostly in non-extension classes) stays on {{Locale.ROOT}} — the input
domain is machine tokens, so ROOT is correct and no doc change is warranted.
h3. Note: existing precedents for machine-facing locale handling
Two existing methods already model what this issue recommends for
machine-facing output — a stable pinned locale rather than {{getDefault()}}:
* {{DateUtilStaticExtensions.parseToStringDate(Date, String)}} — a *GDK
extension method* that hard-bakes {{Locale.US}} because it parses the fixed
{{Date.toString()}} format; {{getDefault()}} would break under a non-US locale.
(No override needed — the format is fixed.)
* {{groovy.json.JsonGenerator}} — a config object with a stable default
({{Locale.US}}, ISO-ish pattern, GMT) *and* an explicit {{Locale}} override:
{code:java}
protected static final Locale JSON_DATE_FORMAT_LOCALE = Locale.US; // stable,
not getDefault()
public Options dateFormat(String format, Locale locale) { ... } //
explicit override
{code}
Together they bracket the pattern: machine/ingestion pins a stable locale
(Category B), and where an override is useful it is offered explicitly
(Category C). The precedent for both already exists in the codebase — including
inside the extension-method surface.
h3. Potential policy
The rule is not "no default-locale methods" — it is "no method depends on the
default locale _silently or without an escape hatch_". Concretely, a new
locale-sensitive GDK method is acceptable when:
* It offers an explicit-{{Locale}} variant (or the non-locale form is
documented as living elsewhere, e.g. {{toBigDecimal()}} for locale-free number
parsing).
* Its no-arg/default behaviour is stated in the Javadoc — which locale it uses
and why.
* The default is chosen by audience, not by habit:
** _Human-facing formatting_ (currency/percent/date-for-display, {{printf}})
*may* default to {{Locale.getDefault()}}, provided the {{Locale}} variant
exists and the default is documented ("uses the JVM's locale; pass a {{Locale}}
for server/per-user output").
** _Machine-facing / ingestion_ (parsing, serialization, anything persisted or
re-parsed) defaults to {{Locale.ROOT}} or requires an explicit {{Locale}} —
never {{getDefault()}}.
Additionally:
* Avoid new locale-sensitive method ships _default-locale-only_ (default
behaviour with neither a {{Locale}} variant nor a documented contract), but if
unavoidable, explicit Javadoc should make the default explicit and point to the
JDK long-hand to get Locale behavior.
was:
h3. Summary
Assess and improve how the GDK handles {{java.util.Locale}}, following the
discussion on the GROOVY-12147 dev thread. The goal is *not* to remove
convenience methods, but to make each locale-sensitive method's contract
explicit, offer an explicit-{{Locale}} variant where one is missing, and fix a
couple of parse defaults before 6.0.0 ships. Almost all of this is
additive/non-breaking.
h3. Background
Two recent efforts touched this area:
* GROOVY-12147 added locale-aware number/currency/percent formatting and
parsing.
* GROOVY-12055 moved identifier/token case-folding to {{Locale.ROOT}}.
On the GROOVY-12147 thread it was noted that methods depending on the JVM
{{default}} locale are hard to test and surprising on servers/multi-locale
processes. The guiding principle we landed on:
* {{Locale.ROOT}} — data meant for machines: protocols, DB, file formats,
anything persisted or re-parsed.
* {{Locale.getDefault()}} — output meant for the human running the code (only a
valid proxy for "the user" in single-user/client contexts).
The work below applies that principle per method.
h3. Scope
All actionable items are in GDK extension classes ({{StringGroovyMethods}},
{{DefaultGroovyMethods}}, {{DateUtilExtensions}}/{{DateUtilStaticExtensions}},
{{DateTimeExtensions}}/{{DateTimeStaticExtensions}}).
h4. A. Case-insensitive / case-folding on human text (consistency + docs)
These take arbitrary user text, fold case, and are silent about their
(locale-independent) contract.
|| Method || Current || Action ||
| {{containsIgnoreCase}} | {{toLowerCase(Locale.ROOT)}} both sides | Align
mechanism with starts/ends; Javadoc "locale-independent" |
| {{startsWithIgnoreCase}}, {{endsWithIgnoreCase}} | JDK {{equalsIgnoreCase}}
(char-wise) | Same doc note; pick one shared mechanism |
| {{capitalize}}, {{uncapitalize}} | {{Character.to{Upper,Lower}Case}} (single
char) | Javadoc note only; low priority |
A {{Locale}} variant is generally *not* warranted here (search/compare wants
stability).
h4. B. Parsing / machine-ingestion
|| Method || Current default || Action ||
| {{CharSequence.toCurrencyNumber()}}, {{toPercentNumber()}} (no-arg) |
{{Locale.getDefault()}} | Change default to {{ROOT}} (or drop the no-arg)
*before 6.0.0*; keep the {{(Locale)}} variant; doc |
| {{CharSequence.toNumber(Locale)}} | {{Locale}}-only, no no-arg — _correct
as-is_ | Leave {{Locale}}-only. The locale-independent parse is already
provided by {{toBigDecimal()}}/{{toInteger()}}/{{as Number}} (Java-literal
grammar, not ROOT). A no-arg would either duplicate those or introduce
divergent {{NumberFormat}} semantics ({{"1,234"}} parses via ROOT
{{NumberFormat}} but throws via {{toBigDecimal}}). |
| {{Date.parse(format, input)}} / +TimeZone (static) | default locale
({{SimpleDateFormat}}) | Add {{(…, Locale)}} variant; doc |
| {{<java.time>.parse(text, pattern)}} ×9 types (static) | default locale
({{ofPattern}}) | Add {{(…, Locale)}} variant; doc |
h4. C. Human-facing formatting defaulting to the JVM default locale
Defensible defaults, but each needs (1) a JavaDoc line "uses the JVM's locale;
pass a {{Locale}} for server/per-user output" and (2) a {{Locale}} variant
where none exists. Or if the Locale variant explosion is not warranted, the
JavaDoc should point to the JDK long-hand for that case.
|| Method || Locale variant today? || Action ||
| {{Number.toCurrencyString()}} / {{toPercentString()}} (no-arg) | yes | Doc
the default; currency no-arg is a drop candidate (ROOT is meaningless for
currency) |
| {{Date.format(String)}}, {{format(String, TimeZone)}},
{{Calendar.format(String)}} | no | Add {{(…, Locale)}} variant + doc |
| {{Date.getDateString()}} / {{getTimeString()}} / {{getDateTimeString()}} | no
| Add a locale-aware variant (or doc as fixed default-locale) |
| {{<java.time>.format(String pattern)}} ×6 | no | Add {{(…, Locale)}} variant
+ doc |
| {{<java.time>.format(FormatStyle)}} ×6 | no | Add {{(…, Locale)}} variant +
doc |
| {{printf(...)}} ×4, {{sprintf(...)}} ×2 | no | Add a {{Locale}}-first
overload (mirrors {{String.format(Locale, …)}}); doc |
h4. Cross-cutting: Javadoc {{@see}} routing
Wire bidirectional {{@see}} links so each method routes the reader to the right
sibling: the locale-aware variant, the locale-free variant, and the raw JDK API
({{NumberFormat.getNumberInstance(Locale)}},
{{DateTimeFormatter.ofPattern(pattern, locale)}}, {{new
SimpleDateFormat(pattern, locale)}}). This makes the "no no-arg is intentional"
decisions self-documenting and keeps the JDK escape hatch one hop away.
h3. Out of scope
The GROOVY-12055 identifier/token case-folding (class/property/CLI/SQL names,
etc., mostly in non-extension classes) stays on {{Locale.ROOT}} — the input
domain is machine tokens, so ROOT is correct and no doc change is warranted.
h3. Note: existing precedents for machine-facing locale handling
Two existing methods already model what this issue recommends for
machine-facing output — a stable pinned locale rather than {{getDefault()}}:
* {{DateUtilStaticExtensions.parseToStringDate(Date, String)}} — a *GDK
extension method* that hard-bakes {{Locale.US}} because it parses the fixed
{{Date.toString()}} format; {{getDefault()}} would break under a non-US locale.
(No override needed — the format is fixed.)
* {{groovy.json.JsonGenerator}} — a config object with a stable default
({{Locale.US}}, ISO-ish pattern, GMT) *and* an explicit {{Locale}} override:
{code:java}
protected static final Locale JSON_DATE_FORMAT_LOCALE = Locale.US; // stable,
not getDefault()
public Options dateFormat(String format, Locale locale) { ... } //
explicit override
{code}
Together they bracket the pattern: machine/ingestion pins a stable locale
(Category B), and where an override is useful it is offered explicitly
(Category C). The precedent for both already exists in the codebase — including
inside the extension-method surface.
h3. Potential policy
The rule is not "no default-locale methods" — it is "no method depends on the
default locale _silently or without an escape hatch_". Concretely, a new
locale-sensitive GDK method is acceptable when:
* It offers an explicit-{{Locale}} variant (or the non-locale form is
documented as living elsewhere, e.g. {{toBigDecimal()}} for locale-free number
parsing).
* Its no-arg/default behaviour is stated in the Javadoc — which locale it uses
and why.
* The default is chosen by audience, not by habit:
** _Human-facing formatting_ (currency/percent/date-for-display, {{printf}})
*may* default to {{Locale.getDefault()}}, provided the {{Locale}} variant
exists and the default is documented ("uses the JVM's locale; pass a {{Locale}}
for server/per-user output").
** _Machine-facing / ingestion_ (parsing, serialization, anything persisted or
re-parsed) defaults to {{Locale.ROOT}} or requires an explicit {{Locale}} —
never {{getDefault()}}.
Additionally:
* Avoid new locale-sensitive method ships _default-locale-only_ (default
behaviour with neither a {{Locale}} variant nor a documented contract), but if
unavoidable, explicit Javadoc should make the default explicit and point to the
JDK long-hand to get Locale behavior.
> Clarify Locale handling throughout the GDK
> ------------------------------------------
>
> Key: GROOVY-12152
> URL: https://issues.apache.org/jira/browse/GROOVY-12152
> Project: Groovy
> Issue Type: Improvement
> Reporter: Paul King
> Priority: Major
>
> h3. Summary
> Assess and improve how the GDK handles {{java.util.Locale}}, following the
> discussion on the GROOVY-12147 dev thread. The goal is *not* to remove
> convenience methods, but to make each locale-sensitive method's contract
> explicit, offer an explicit-{{Locale}} variant where one is missing, and fix
> a couple of parse defaults before 6.0.0 ships. Almost all of this is
> additive/non-breaking.
> h3. Background
> Two recent efforts touched this area:
> * GROOVY-12147 added locale-aware number/currency/percent formatting and
> parsing.
> * GROOVY-12055 moved identifier/token case-folding to {{Locale.ROOT}}.
> On the GROOVY-12147 thread it was noted that methods depending on the JVM
> {{default}} locale are hard to test and surprising on servers/multi-locale
> processes. A guiding principle we might want to land on (to match the JDK
> stance):
> * {{Locale.ROOT}} — data meant for machines: protocols, DB, file formats,
> anything persisted or re-parsed.
> * {{Locale.getDefault()}} — output meant for the human running the code (only
> a valid proxy for "the user" in single-user/client contexts).
> The catch is that {{Locale.getDefault()}} is only a good proxy for "the
> human" when there is one human and they _are_ the process:
> * On the *command line, a script, or a desktop app*, the JVM default
> genuinely reflects the person reading the output — the default-locale
> convenience is exactly right.
> * On a *web server, batch job, or any multi-tenant process*, the JVM default
> is _not_ the end user's locale — it is the container's ({{LANG}}, whatever
> ops set, usually en-US or C/POSIX). The actual user is determined *per
> request* (Accept-Language, a profile setting) and arrives as data, not as the
> process default. There, a default-locale method silently formats for the
> wrong human, and it looks correct in dev only because the dev box default
> happens to match the test expectations.
> This is also why default-locale methods are hard to test meaningfully (you
> have to pin a locale, which defeats the "default"), and why the convenience
> must always be paired with an explicit-{{Locale}} variant: the same call has
> a different "correct" answer depending on where it runs, and only the caller
> knows which deployment it is in.
> The items below are potential work we might want to do to get closer to the
> above guiding principle.
> h3. Scope
> All actionable items are in GDK extension classes ({{StringGroovyMethods}},
> {{DefaultGroovyMethods}},
> {{DateUtilExtensions}}/{{DateUtilStaticExtensions}},
> {{DateTimeExtensions}}/{{DateTimeStaticExtensions}}).
> h4. A. Case-insensitive / case-folding on human text (consistency + docs)
> These take arbitrary user text, fold case, and are silent about their
> (locale-independent) contract.
> || Method || Current || Action ||
> | {{containsIgnoreCase}} | {{toLowerCase(Locale.ROOT)}} both sides | Align
> mechanism with starts/ends; Javadoc "locale-independent" |
> | {{startsWithIgnoreCase}}, {{endsWithIgnoreCase}} | JDK {{equalsIgnoreCase}}
> (char-wise) | Same doc note; pick one shared mechanism |
> | {{capitalize}}, {{uncapitalize}} | {{Character.to{Upper,Lower}Case}}
> (single char) | Javadoc note only; low priority |
> A {{Locale}} variant is generally *not* warranted here (search/compare wants
> stability).
> h4. B. Parsing / machine-ingestion
> || Method || Current default || Action ||
> | {{CharSequence.toCurrencyNumber()}}, {{toPercentNumber()}} (no-arg) |
> {{Locale.getDefault()}} | Change default to {{ROOT}} (or drop the no-arg)
> *before 6.0.0*; keep the {{(Locale)}} variant; doc |
> | {{CharSequence.toNumber(Locale)}} | {{Locale}}-only, no no-arg — _correct
> as-is_ | Leave {{Locale}}-only. The locale-independent parse is already
> provided by {{toBigDecimal()}}/{{toInteger()}}/{{as Number}} (Java-literal
> grammar, not ROOT). A no-arg would either duplicate those or introduce
> divergent {{NumberFormat}} semantics ({{"1,234"}} parses via ROOT
> {{NumberFormat}} but throws via {{toBigDecimal}}). |
> | {{Date.parse(format, input)}} / +TimeZone (static) | default locale
> ({{SimpleDateFormat}}) | Add {{(…, Locale)}} variant; doc |
> | {{<java.time>.parse(text, pattern)}} ×9 types (static) | default locale
> ({{ofPattern}}) | Add {{(…, Locale)}} variant; doc |
> h4. C. Human-facing formatting defaulting to the JVM default locale
> Defensible defaults, but each needs (1) a JavaDoc line "uses the JVM's
> locale; pass a {{Locale}} for server/per-user output" and (2) a {{Locale}}
> variant where none exists. Or if the Locale variant explosion is not
> warranted, the JavaDoc should point to the JDK long-hand for that case.
> || Method || Locale variant today? || Action ||
> | {{Number.toCurrencyString()}} / {{toPercentString()}} (no-arg) | yes | Doc
> the default; currency no-arg is a drop candidate (ROOT is meaningless for
> currency) |
> | {{Date.format(String)}}, {{format(String, TimeZone)}},
> {{Calendar.format(String)}} | no | Add {{(…, Locale)}} variant + doc |
> | {{Date.getDateString()}} / {{getTimeString()}} / {{getDateTimeString()}} |
> no | Add a locale-aware variant (or doc as fixed default-locale) |
> | {{<java.time>.format(String pattern)}} ×6 | no | Add {{(…, Locale)}}
> variant + doc |
> | {{<java.time>.format(FormatStyle)}} ×6 | no | Add {{(…, Locale)}} variant +
> doc |
> | {{printf(...)}} ×4, {{sprintf(...)}} ×2 | no | Add a {{Locale}}-first
> overload (mirrors {{String.format(Locale, …)}}); doc |
> h4. Cross-cutting: Javadoc {{@see}} routing
> Wire bidirectional {{@see}} links so each method routes the reader to the
> right sibling: the locale-aware variant, the locale-free variant, and the raw
> JDK API ({{NumberFormat.getNumberInstance(Locale)}},
> {{DateTimeFormatter.ofPattern(pattern, locale)}}, {{new
> SimpleDateFormat(pattern, locale)}}). This makes the "no no-arg is
> intentional" decisions self-documenting and keeps the JDK escape hatch one
> hop away.
> h3. Out of scope
> The GROOVY-12055 identifier/token case-folding (class/property/CLI/SQL names,
> etc., mostly in non-extension classes) stays on {{Locale.ROOT}} — the input
> domain is machine tokens, so ROOT is correct and no doc change is warranted.
> h3. Note: existing precedents for machine-facing locale handling
> Two existing methods already model what this issue recommends for
> machine-facing output — a stable pinned locale rather than {{getDefault()}}:
> * {{DateUtilStaticExtensions.parseToStringDate(Date, String)}} — a *GDK
> extension method* that hard-bakes {{Locale.US}} because it parses the fixed
> {{Date.toString()}} format; {{getDefault()}} would break under a non-US
> locale. (No override needed — the format is fixed.)
> * {{groovy.json.JsonGenerator}} — a config object with a stable default
> ({{Locale.US}}, ISO-ish pattern, GMT) *and* an explicit {{Locale}} override:
> {code:java}
> protected static final Locale JSON_DATE_FORMAT_LOCALE = Locale.US; //
> stable, not getDefault()
> public Options dateFormat(String format, Locale locale) { ... } //
> explicit override
> {code}
> Together they bracket the pattern: machine/ingestion pins a stable locale
> (Category B), and where an override is useful it is offered explicitly
> (Category C). The precedent for both already exists in the codebase —
> including inside the extension-method surface.
> h3. Potential policy
> The rule is not "no default-locale methods" — it is "no method depends on the
> default locale _silently or without an escape hatch_". Concretely, a new
> locale-sensitive GDK method is acceptable when:
> * It offers an explicit-{{Locale}} variant (or the non-locale form is
> documented as living elsewhere, e.g. {{toBigDecimal()}} for locale-free
> number parsing).
> * Its no-arg/default behaviour is stated in the Javadoc — which locale it
> uses and why.
> * The default is chosen by audience, not by habit:
> ** _Human-facing formatting_ (currency/percent/date-for-display, {{printf}})
> *may* default to {{Locale.getDefault()}}, provided the {{Locale}} variant
> exists and the default is documented ("uses the JVM's locale; pass a
> {{Locale}} for server/per-user output").
> ** _Machine-facing / ingestion_ (parsing, serialization, anything persisted
> or re-parsed) defaults to {{Locale.ROOT}} or requires an explicit {{Locale}}
> — never {{getDefault()}}.
> Additionally:
> * Avoid new locale-sensitive method ships _default-locale-only_ (default
> behaviour with neither a {{Locale}} variant nor a documented contract), but
> if unavoidable, explicit Javadoc should make the default explicit and point
> to the JDK long-hand to get Locale behavior.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)