This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26509-type-conversion-from-type in repository https://gitbox.apache.org/repos/asf/camel.git
commit eb275279a5484235320694158e544c45448d5a6d Author: Claus Ibsen <[email protected]> AuthorDate: Tue Nov 3 15:45:33 2026 +0100 CAMEL-26509: camel-core - TypeConversionException gives the real from type, and ArrayTypeConverter gives no conversion for an element that does not fit When a converter failed during the conversion of a message, the registry built the TypeConversionException from the value prepared for logging, which is a String. So the message always said "from type: java.lang.String", and getValue()/getFromType() gave that String. The message no longer includes the value (CAMEL-24553), so the exception now gets the value itself. ArrayTypeConverter filled the target array with Array.set and Collection.toArray, which throw IllegalArgumentException (argument type mismatch) or ArrayStoreException when an element does not fit the component type, such as a list of strings to int[] or byte[]. It now returns null (no conversion) so other converters can try. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../camel/impl/converter/ArrayTypeConverter.java | 37 ++++++----- .../impl/converter/CoreTypeConverterRegistry.java | 12 +--- .../converter/CoreTypeConverterRegistryTest.java | 71 ++++++++++++++++++++++ .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 12 ++++ 4 files changed, 106 insertions(+), 26 deletions(-) diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/converter/ArrayTypeConverter.java b/core/camel-base/src/main/java/org/apache/camel/impl/converter/ArrayTypeConverter.java index b053bed4ff52..ce1f73048bce 100644 --- a/core/camel-base/src/main/java/org/apache/camel/impl/converter/ArrayTypeConverter.java +++ b/core/camel-base/src/main/java/org/apache/camel/impl/converter/ArrayTypeConverter.java @@ -35,24 +35,29 @@ public class ArrayTypeConverter extends TypeConverterSupport { @SuppressWarnings("unchecked") public <T> T convertTo(Class<T> type, Exchange exchange, Object value) { if (type.isArray()) { - if (value instanceof Collection collection) { - Object array = Array.newInstance(type.getComponentType(), collection.size()); - if (array instanceof Object[]) { - collection.toArray((Object[]) array); - } else { - int index = 0; - for (Object element : collection) { - Array.set(array, index++, element); + // an element that does not fit the component type of the array cannot be converted + try { + if (value instanceof Collection collection) { + Object array = Array.newInstance(type.getComponentType(), collection.size()); + if (array instanceof Object[]) { + collection.toArray((Object[]) array); + } else { + int index = 0; + for (Object element : collection) { + Array.set(array, index++, element); + } } + return (T) array; + } else if (value != null && value.getClass().isArray()) { + int size = Array.getLength(value); + Object answer = Array.newInstance(type.getComponentType(), size); + for (int i = 0; i < size; i++) { + Array.set(answer, i, Array.get(value, i)); + } + return (T) answer; } - return (T) array; - } else if (value != null && value.getClass().isArray()) { - int size = Array.getLength(value); - Object answer = Array.newInstance(type.getComponentType(), size); - for (int i = 0; i < size; i++) { - Array.set(answer, i, Array.get(value, i)); - } - return (T) answer; + } catch (IllegalArgumentException | ArrayStoreException e) { + return null; } } else if (Collection.class.isAssignableFrom(type) && type.isAssignableFrom(ArrayList.class)) { // only for collection types a list can be assigned to (such as not a Set) diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/converter/CoreTypeConverterRegistry.java b/core/camel-base/src/main/java/org/apache/camel/impl/converter/CoreTypeConverterRegistry.java index 53a2d7180665..e81419dd5eb8 100644 --- a/core/camel-base/src/main/java/org/apache/camel/impl/converter/CoreTypeConverterRegistry.java +++ b/core/camel-base/src/main/java/org/apache/camel/impl/converter/CoreTypeConverterRegistry.java @@ -40,7 +40,6 @@ import org.apache.camel.spi.CamelLogger; import org.apache.camel.spi.Injector; import org.apache.camel.spi.TypeConverterRegistry; import org.apache.camel.spi.TypeConvertible; -import org.apache.camel.support.MessageHelper; import org.apache.camel.support.TypeConverterSupport; import org.apache.camel.support.service.ServiceSupport; import org.apache.camel.util.ObjectHelper; @@ -660,15 +659,8 @@ public abstract class CoreTypeConverterRegistry extends ServiceSupport implement return (TypeConversionException) cause; } } - Object body; - // extract the body for logging which allows to limit the message body in the exception/stacktrace - // and also can be used to turn off logging sensitive message data - if (exchange != null) { - body = MessageHelper.extractValueForLogging(value, exchange.getIn()); - } else { - body = value; - } - return new TypeConversionException(body, type, cause); + // the message of the exception does not include the value, so use the value itself which gives the real from type + return new TypeConversionException(value, type, cause); } public Statistics getStatistics() { diff --git a/core/camel-core/src/test/java/org/apache/camel/impl/converter/CoreTypeConverterRegistryTest.java b/core/camel-core/src/test/java/org/apache/camel/impl/converter/CoreTypeConverterRegistryTest.java index 8f7a57e19508..c13a0b566e36 100644 --- a/core/camel-core/src/test/java/org/apache/camel/impl/converter/CoreTypeConverterRegistryTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/impl/converter/CoreTypeConverterRegistryTest.java @@ -17,6 +17,7 @@ package org.apache.camel.impl.converter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -25,17 +26,22 @@ import java.util.Set; import org.apache.camel.ContextTestSupport; import org.apache.camel.Exchange; +import org.apache.camel.NoTypeConversionAvailableException; +import org.apache.camel.TypeConversionException; import org.apache.camel.TypeConverter; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.spi.TypeConverterRegistry; import org.apache.camel.spi.TypeConvertible; +import org.apache.camel.support.DefaultExchange; import org.apache.camel.support.TypeConverterSupport; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; public class CoreTypeConverterRegistryTest extends ContextTestSupport { @@ -311,4 +317,69 @@ public class CoreTypeConverterRegistryTest extends ContextTestSupport { assertInstanceOf(Set.class, tc.convertTo(Set.class, new String[] { "a", "b" })); assertNull(tc.convertTo(Set.class, new int[] { 1, 2 })); } + + @Test + public void testTypeConversionExceptionHasFromTypeOfValue() { + context.getTypeConverterRegistry().addTypeConverter(Foo.class, Base.class, new TypeConverterSupport() { + @Override + public <T> T convertTo(Class<T> type, Exchange exchange, Object value) { + throw new IllegalStateException("cannot convert"); + } + }); + TypeConverter tc = context.getTypeConverter(); + Exchange exchange = new DefaultExchange(context); + Sub value = new Sub(); + + TypeConversionException e = assertThrows(TypeConversionException.class, + () -> tc.convertTo(Foo.class, exchange, value)); + assertSame(value, e.getValue()); + assertEquals(Sub.class, e.getFromType()); + assertEquals(Foo.class, e.getToType()); + assertTrue(e.getMessage().startsWith("Error during type conversion from type: " + Sub.class.getCanonicalName()), + e.getMessage()); + + e = assertThrows(TypeConversionException.class, () -> tc.mandatoryConvertTo(Foo.class, exchange, value)); + assertSame(value, e.getValue()); + assertEquals(Sub.class, e.getFromType()); + } + + @Test + public void testCollectionWithElementThatDoesNotFitArray() throws Exception { + TypeConverter tc = context.getTypeConverter(); + Exchange exchange = new DefaultExchange(context); + + // a primitive array + assertNull(tc.convertTo(int[].class, exchange, List.of("a", "b"))); + assertNull(tc.tryConvertTo(int[].class, exchange, List.of("a", "b"))); + assertThrows(NoTypeConversionAvailableException.class, + () -> tc.mandatoryConvertTo(int[].class, exchange, List.of("a", "b"))); + // a null element does not fit a primitive array + assertNull(tc.convertTo(int[].class, exchange, Arrays.asList(1, null))); + // an object array + assertNull(tc.convertTo(Integer[].class, exchange, List.of("a", "b"))); + // CAMEL-26137: a list of strings to byte[] failed with argument type mismatch + assertNull(tc.convertTo(byte[].class, exchange, new ArrayList<>(List.of("a", "b")))); + + // elements that fit still convert, also after the converter was promoted for the collection type + assertArrayEquals(new int[] { 1, 2 }, tc.convertTo(int[].class, exchange, List.of(1, 2))); + assertNull(tc.convertTo(int[].class, exchange, List.of("a", "b"))); + assertArrayEquals(new Integer[] { 1, 2 }, tc.convertTo(Integer[].class, exchange, List.of(1, 2))); + assertNull(tc.convertTo(Integer[].class, exchange, List.of("a", "b"))); + } + + @Test + public void testArrayWithElementThatDoesNotFitArray() throws Exception { + TypeConverter tc = context.getTypeConverter(); + Exchange exchange = new DefaultExchange(context); + + assertNull(tc.convertTo(int[].class, exchange, new Object[] { "a", "b" })); + assertNull(tc.convertTo(int[].class, exchange, new Integer[] { 1, null })); + assertNull(tc.convertTo(Integer[].class, exchange, new Object[] { "a", "b" })); + assertThrows(NoTypeConversionAvailableException.class, + () -> tc.mandatoryConvertTo(int[].class, exchange, new Object[] { "a", "b" })); + + // elements that fit still convert + assertArrayEquals(new int[] { 1, 2 }, tc.convertTo(int[].class, exchange, new Object[] { 1, 2 })); + assertArrayEquals(new Integer[] { 1, 2 }, tc.convertTo(Integer[].class, exchange, new int[] { 1, 2 })); + } } diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc index 7c82ebd18930..a90240dafbb2 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc @@ -315,6 +315,18 @@ graphql, kubernetes-custom-resources and typesafe-ai) now have type converters t or `InputStream`, so such a body sent to http or a file failed. A `JsonObject` or `JsonArray` that holds a value that is not JSON, such as a POJO, converts as before. +=== camel-core - TypeConversionException gives the value that failed to convert + +When a type converter fails during the conversion of a message, the `TypeConversionException` now gives the value that +failed to convert from `getValue()`, and its type from `getFromType()` and in the message (`Error during type +conversion from type: ...`). Before, they gave the value as a `String` prepared for logging, so the message always +said `from type: java.lang.String`. The message does not include the value itself. + +The conversion of a collection or array to an array whose component type does not fit an element, such as a list of +strings to `int[]` or `byte[]`, now gives no conversion (`null`, or `NoTypeConversionAvailableException` for a +mandatory conversion), and other type converters can try. Before, it failed with a `TypeConversionException` due to +`IllegalArgumentException: argument type mismatch` or an `ArrayStoreException`. + === camel-core - a route called from a doCatch handles its failure after the doTry called a route that failed When the `doTry` block calls a route (such as with `direct`) that fails without handling the failure (no `onException`
