shashank created CAMEL-25703:
--------------------------------
Summary: camel-bindy - a BigDecimal, Double or Float field without
precision is marshalled with at most three decimals (1.23456 is written as
1.235, 0.000123 as 0), so data is silently rounded and a record does not round
trip
Key: CAMEL-25703
URL: https://issues.apache.org/jira/browse/CAMEL-25703
Project: Camel
Issue Type: Bug
Components: camel-bindy
Reporter: shashank
Assignee: shashank
Since CAMEL-24168 (4.22.0) the default {{precision}} of {{@DataField}} and
{{@KeyValuePairField}} is {{-1}}, documented as "Use -1 (default) to preserve
the original scale from the input". Unmarshal now keeps the scale, but marshal
does not keep the decimals.
{{AbstractNumberFormat}} (main d46d51c24550, lines 50-65) creates a {{new
DecimalFormat()}} and, for a precision of {{-1}}, does not set the fraction
digits at all ("skip DecimalFormat fraction digit configuration when precision
is unset", PR #24919). The default pattern of {{DecimalFormat}} is
{{#,##0.###}}: at most three fraction digits, rounded {{HALF_EVEN}}.
{{BigDecimalFormatFactory}}, {{DoubleFormatFactory}} and {{FloatFormatFactory}}
format with it, so every value with more than three decimals is rounded when it
is written, without an error or a log line:
||field type||value||written on main||
|{{BigDecimal}}|{{1.23456}}|{{1.235}}|
|{{BigDecimal}}|{{0.98765}}|{{0.988}}|
|{{Double}}|{{12.3456789}}|{{12.346}}|
|{{Double}}|{{0.000123}}|{{0}}|
Unmarshal reads {{1.23456}} correctly, so a route that unmarshals a CSV,
fixed-length or key-value-pair record and marshals it again changes the
numbers. Exchange rates, unit prices, quantities and coordinates with four or
more decimals are common in the files Bindy is used for, and the default
annotation ({{@DataField(pos = 1)}}) is enough to hit it. The only test of the
unset case ({{BindyBigDecimalNoPrecisionTest}}, added by CAMEL-24168) uses
{{10.35}}, two decimals, so it passes.
Trigger: default options; a {{BigDecimal}}, {{Double}} or {{Float}} field with
neither {{precision}} nor {{pattern}}; any value with more than three decimals;
{{marshal().bindy(...)}} of type Csv, Fixed or KeyValue.
h3. Reproduction
New {{BindyNumberWithoutPrecisionKeepsDecimalsTest}} (5 tests, locale {{en}}):
* CSV marshal of {{BigDecimal 1.23456}}, {{Double 0.000123}}, {{Float 1.1}}:
expected {{1.23456,0.000123,1.1}}, main writes {{1.235,0,1.1}};
* CSV unmarshal of {{0.98765,12.3456789,2.5}} then marshal: main writes
{{0.988,12.346,2.5}};
* fixed-length record ({{BigDecimal}} and {{Double}}, length 10 each)
unmarshalled and marshalled: {{ 1.23456 0.000123}} becomes {{ 1.235
0}};
* controls (pass on main and with the fix): {{10}}, {{10.0}} and {{0.5f}} are
written as {{10,10,0.5}}; the whole-number float {{123456792f}} is written as
{{123456792}}.
On main 3 of the first 4 fail (the first control passes; the whole-number float
control was not run against main, which writes that float through the same
{{double}} path). With the fix the five pass, and the camel-bindy tests: 243
run, 0 failures, 0 errors, 3 skipped.
h3. Proposed fix
* {{AbstractNumberFormat}}: when no precision is set (and no implied decimal
separator), {{setMaximumFractionDigits(Integer.MAX_VALUE)}}, so the formatter
keeps every decimal of a {{BigDecimal}} and the shortest decimal form of a
{{double}} ({{DecimalFormat}} caps a {{double}} at 340 fraction digits itself).
Grouping, locale symbols and the whole-number output ({{10}}, not {{10.0}}) are
unchanged.
* {{FloatFormatFactory}}: a finite {{Float}} with a fraction part and without
precision is formatted through {{new BigDecimal(Float.toString(value))}};
otherwise the widening to {{double}} would now show as {{1.100000023841858}}
instead of {{1.1}}. A whole-number {{Float}} keeps the exact digits it had
before ({{123456792}}, not the shorter {{123456790}} of {{Float.toString}}).
* An explicit {{precision}}, a {{pattern}} and {{impliedDecimalSeparator}} are
not affected.
Users can notice that such values are written with all their decimals, also a
{{Double}} that is the result of a calculation ({{0.1 + 0.2}} is written
{{0.30000000000000004}}, before {{0.3}}); a note goes into the 4.24 upgrade
guide.
How it was found: round r29, track I (silent data corruption in data formats),
a metamorphic check "unmarshal then marshal is the identity on valid input"
applied by reading the number formats of Bindy; a JShell probe of {{new
DecimalFormat()}} with grouping off gave {{12.346}} for {{12.34567}} and {{0}}
for {{0.000123}}, then the test above on the real data format.
Affected: main (d46d51c24550; tested). camel-4.23.x and camel-4.22.x have the
same {{AbstractNumberFormat}} lines and {{precision() default -1}} (read, not
run). camel-4.18.x and camel-4.14.x still have the older code of CAMEL-24168
(precision 0, all decimals dropped), which is that ticket, not this one.
Duplicate check (2026-10-16): JIRA text "bindy BigDecimal", "bindy precision",
"bindy decimal digits", "bindy fraction digits", "bindy rounded three decimals"
and the camel-bindy issues created since 2026-06-01 (CAMEL-24168 is the only
related one, resolved; CAMEL-25150, 25151, 25152 are other defects); GitHub
pull requests "bindy precision", "bindy decimal" and "bindy in:title" (PR
#24919 for CAMEL-24168, merged; nothing open).
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)