This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch fix/CAMEL-24168
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 4f1e7ebe632d09566e479ad2e90bf38a61b4fc42
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Jul 20 09:11:55 2026 +0200

    CAMEL-24168: Fix BigDecimal precision handling when no explicit precision 
set
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../src/main/docs/bindy-dataformat.adoc            |   5 +-
 .../dataformat/bindy/annotation/DataField.java     |   5 +-
 .../bindy/annotation/KeyValuePairField.java        |   4 +-
 .../bindy/format/AbstractNumberFormat.java         |   6 +-
 .../format/factories/BigDecimalFormatFactory.java  |   8 +-
 .../bindy/csv/BindyBigDecimalNoPrecisionTest.java  | 148 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc    |  13 ++
 7 files changed, 178 insertions(+), 11 deletions(-)

diff --git a/components/camel-bindy/src/main/docs/bindy-dataformat.adoc 
b/components/camel-bindy/src/main/docs/bindy-dataformat.adoc
index 80e236ddf619..5e060e0c1415 100644
--- a/components/camel-bindy/src/main/docs/bindy-dataformat.adoc
+++ b/components/camel-bindy/src/main/docs/bindy-dataformat.adoc
@@ -483,7 +483,8 @@ use "default" to use platform default locale.
 the field in the CSV generated (output message) must be different compare to 
input position (pos). See the pos
 parameter.
 
-| precision | int |  | 0 | precision of the {@link java.math.BigDecimal} 
number to be created
+| precision | int |  | -1 a| precision of the {@link java.math.BigDecimal} 
number to be created. Use -1 (default) to preserve the original
+scale from the input.
 
 | required | boolean |  | false | Indicates if the field is mandatory
 
@@ -1346,7 +1347,7 @@ to @Message.continueParseOnFailure.
 | position | int |  | 0 a| Position of the field in the message generated - 
must be used when the position of the key/tag in the FIX message
 must be different
 
-| precision | int |  | 0 | precision of the BigDecimal number to be created
+| precision | int |  | -1 | precision of the BigDecimal number to be created. 
Use -1 (default) to preserve the original scale from the input.
 
 | required | boolean |  | false | Indicates if the field is mandatory
 
diff --git 
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/DataField.java
 
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/DataField.java
index 4ef891393cd3..846cc6f0427c 100644
--- 
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/DataField.java
+++ 
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/DataField.java
@@ -86,9 +86,10 @@ public @interface DataField {
     char paddingChar() default ' ';
 
     /**
-     * precision of the {@link java.math.BigDecimal} number to be created
+     * precision of the {@link java.math.BigDecimal} number to be created. Use 
-1 (default) to preserve the original
+     * scale from the input.
      */
-    int precision() default 0;
+    int precision() default -1;
 
     /**
      * Position of the field in the output message generated (should start 
from 1). Must be used when the position of
diff --git 
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/KeyValuePairField.java
 
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/KeyValuePairField.java
index 87f2fc2cab26..e289c62296ed 100644
--- 
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/KeyValuePairField.java
+++ 
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/KeyValuePairField.java
@@ -70,11 +70,11 @@ public @interface KeyValuePairField {
     int position() default 0;
 
     /**
-     * precision of the BigDecimal number to be created
+     * precision of the BigDecimal number to be created. Use -1 (default) to 
preserve the original scale from the input.
      *
      * @return int
      */
-    int precision() default 0;
+    int precision() default -1;
 
     /**
      * Indicates if the field is mandatory
diff --git 
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java
 
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java
index cd8e92515ce1..742761d00ef5 100644
--- 
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java
+++ 
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java
@@ -43,7 +43,7 @@ public abstract class AbstractNumberFormat<T extends Number> 
implements Format<T
      */
     protected AbstractNumberFormat(boolean impliedDecimalPosition, int 
precision, Locale locale) {
         this.impliedDecimalPosition = impliedDecimalPosition;
-        this.precision = precision > 0 ? precision : 0;
+        this.precision = precision;
         this.format = null;
         this.multiplier = 1;
 
@@ -58,8 +58,8 @@ public abstract class AbstractNumberFormat<T extends Number> 
implements Format<T
         if (this.impliedDecimalPosition) {
             this.format.setMinimumFractionDigits(0);
             this.format.setMaximumFractionDigits(0);
-            this.multiplier = Math.pow(10D, precision);
-        } else {
+            this.multiplier = Math.pow(10D, Math.max(precision, 0));
+        } else if (this.precision >= 0) {
             this.format.setMinimumFractionDigits(this.precision);
             this.format.setMaximumFractionDigits(this.precision);
         }
diff --git 
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/BigDecimalFormatFactory.java
 
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/BigDecimalFormatFactory.java
index db0d2da62bc7..3a0a2236b214 100644
--- 
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/BigDecimalFormatFactory.java
+++ 
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/BigDecimalFormatFactory.java
@@ -41,13 +41,17 @@ public class BigDecimalFormatFactory extends 
AbstractFormatFactory {
         return new BigDecimalFormat(
                 formattingOptions.isImpliedDecimalSeparator(),
                 formattingOptions.getPrecision(),
+                formattingOptions.getRounding(),
                 formattingOptions.getLocale());
     }
 
     private static class BigDecimalFormat extends 
AbstractNumberFormat<BigDecimal> {
 
-        BigDecimalFormat(boolean impliedDecimalPosition, int precision, Locale 
locale) {
+        private final String rounding;
+
+        BigDecimalFormat(boolean impliedDecimalPosition, int precision, String 
rounding, Locale locale) {
             super(impliedDecimalPosition, precision, locale);
+            this.rounding = rounding;
         }
 
         @Override
@@ -64,7 +68,7 @@ public class BigDecimalFormatFactory extends 
AbstractFormatFactory {
                 result = 
result.divide(BigDecimal.valueOf(super.getMultiplier()), super.getPrecision(), 
RoundingMode.HALF_EVEN);
             } else {
                 if (super.getPrecision() != -1) {
-                    result = result.setScale(super.getPrecision());
+                    result = result.setScale(super.getPrecision(), 
RoundingMode.valueOf(rounding));
                 }
             }
             return result;
diff --git 
a/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindyBigDecimalNoPrecisionTest.java
 
b/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindyBigDecimalNoPrecisionTest.java
new file mode 100644
index 000000000000..8823edcd128c
--- /dev/null
+++ 
b/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindyBigDecimalNoPrecisionTest.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dataformat.bindy.csv;
+
+import java.math.BigDecimal;
+
+import org.apache.camel.EndpointInject;
+import org.apache.camel.Exchange;
+import org.apache.camel.Produce;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.dataformat.bindy.annotation.CsvRecord;
+import org.apache.camel.dataformat.bindy.annotation.DataField;
+import org.apache.camel.model.dataformat.BindyDataFormat;
+import org.apache.camel.model.dataformat.BindyType;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * Tests that BigDecimal fields without an explicit precision attribute 
round-trip correctly (CAMEL-24168).
+ */
+public class BindyBigDecimalNoPrecisionTest extends CamelTestSupport {
+
+    @Produce("direct:unmarshal")
+    private ProducerTemplate unmarshalTemplate;
+
+    @EndpointInject("mock:unmarshal-result")
+    private MockEndpoint unmarshalResult;
+
+    @Produce("direct:marshal")
+    private ProducerTemplate marshalTemplate;
+
+    @EndpointInject("mock:marshal-result")
+    private MockEndpoint marshalResult;
+
+    @Test
+    public void testUnmarshalBigDecimalWithoutPrecision() throws Exception {
+        unmarshalResult.expectedMessageCount(1);
+
+        unmarshalTemplate.sendBody("10.35,foo");
+
+        unmarshalResult.assertIsSatisfied();
+
+        Exchange exchange = unmarshalResult.getReceivedExchanges().get(0);
+        NoPrecisionModel model = 
exchange.getIn().getBody(NoPrecisionModel.class);
+        assertNotNull(model);
+        assertEquals(new BigDecimal("10.35"), model.amount);
+        assertEquals("foo", model.label);
+    }
+
+    @Test
+    public void testMarshalBigDecimalWithoutPrecision() throws Exception {
+        marshalResult.expectedMessageCount(1);
+
+        NoPrecisionModel model = new NoPrecisionModel();
+        model.amount = new BigDecimal("10.35");
+        model.label = "foo";
+
+        marshalTemplate.sendBody(model);
+
+        marshalResult.assertIsSatisfied();
+
+        String body = 
marshalResult.getReceivedExchanges().get(0).getIn().getBody(String.class);
+        assertEquals("10.35,foo\r\n", body);
+    }
+
+    @Test
+    public void testUnmarshalBigDecimalWithExplicitPrecision() throws 
Exception {
+        unmarshalResult.expectedMessageCount(1);
+
+        unmarshalTemplate.sendBody("10.355,bar");
+
+        unmarshalResult.assertIsSatisfied();
+
+        Exchange exchange = unmarshalResult.getReceivedExchanges().get(0);
+        NoPrecisionModel model = 
exchange.getIn().getBody(NoPrecisionModel.class);
+        assertNotNull(model);
+        // amount has no precision — original scale preserved
+        assertEquals(new BigDecimal("10.355"), model.amount);
+    }
+
+    @Test
+    public void testUnmarshalIntegerValueBigDecimal() throws Exception {
+        unmarshalResult.expectedMessageCount(1);
+
+        unmarshalTemplate.sendBody("10,bar");
+
+        unmarshalResult.assertIsSatisfied();
+
+        Exchange exchange = unmarshalResult.getReceivedExchanges().get(0);
+        NoPrecisionModel model = 
exchange.getIn().getBody(NoPrecisionModel.class);
+        assertNotNull(model);
+        assertEquals(new BigDecimal("10"), model.amount);
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                BindyDataFormat unmarshalBindy = new BindyDataFormat()
+                        .type(BindyType.Csv)
+                        .classType(NoPrecisionModel.class)
+                        .locale("en");
+
+                BindyDataFormat marshalBindy = new BindyDataFormat()
+                        .type(BindyType.Csv)
+                        .classType(NoPrecisionModel.class)
+                        .locale("en");
+
+                from("direct:unmarshal")
+                        .unmarshal(unmarshalBindy)
+                        .to("mock:unmarshal-result");
+
+                from("direct:marshal")
+                        .marshal(marshalBindy)
+                        .to("mock:marshal-result");
+            }
+        };
+    }
+
+    @CsvRecord(separator = ",")
+    public static class NoPrecisionModel {
+        @DataField(pos = 1)
+        public BigDecimal amount;
+
+        @DataField(pos = 2)
+        public String label;
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index 1fb3f0dadfe7..7731d7e7cc54 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -725,6 +725,19 @@ The page-blob `getRange` calculation has been corrected 
from `end - start` to `e
 Azure page blob ranges are inclusive on both ends. If you had a workaround 
adjusting the range values to
 compensate for this off-by-one, it should be removed.
 
+=== camel-bindy - BigDecimal precision default changed
+
+The `@DataField.precision()` attribute default has changed from `0` to `-1` 
(meaning "unset").
+Previously, a `BigDecimal` field without an explicit `precision` would throw 
`ArithmeticException`
+on unmarshal when the input had decimal places, and silently truncated 
decimals to zero on marshal.
+With this fix, omitting `precision` preserves the original scale from the 
input value.
+
+The `@DataField.rounding()` attribute is now honored for `BigDecimal` fields 
that do not use a `pattern`.
+Previously it was only applied when a `pattern` was specified.
+
+If you relied on the implicit `precision=0` behavior to round `BigDecimal` 
values to integers,
+add `precision = 0` explicitly to your `@DataField` annotation.
+
 === camel-xslt / camel-xslt-saxon - transformerFactoryConfigurationStrategy 
now honored
 
 The `transformerFactoryConfigurationStrategy` option is now applied on all 
factory creation paths.

Reply via email to