Croway commented on PR #1995:
URL:
https://github.com/apache/camel-spring-boot/pull/1995#issuecomment-5812009239
Follow-up to my previous comment: I found how the `JacksonFeatureSet` bind
failure happens even when **no** `ObjectMapper` property is set.
**Trigger: a non-enumerable `PropertySource` in the environment.** Spring
Boot can't list the keys of such a source, so it can't rule out that nested
keys under `camel.component.salesforce.*` exist. The binder then:
1. creates a `SalesforceEndpointConfig` for `config`;
2. walks its `ObjectMapper` down to
`serializer-provider.generator.write-capabilities`;
3. fails there with `InstantiationException`.
On current `main` (Spring Boot 4.1.1), with no salesforce properties set at
all, registering one empty non-enumerable source is enough to reproduce the
exact error:
```
Failed to bind properties under
'camel.component.salesforce.config.object-mapper.serializer-provider.generator.write-capabilities'
to
com.fasterxml.jackson.core.util.JacksonFeatureSet<com.fasterxml.jackson.core.StreamWriteCapability>
root: java.lang.InstantiationException
```
So the problem is real, and I was wrong earlier to suggest it only happens
with explicitly set keys. The concerns about the current approach still apply,
though: turning every `object`/`duration` option into `String` silently drops
normal nested settings such as `config.api-version` and
`config.object-mapper=#bean:…`, and it changes public setter types across all
starters.
**Narrower fix: not obvious yet.** The obvious narrowing doesn't work. That
would be keeping nested binding for Camel configuration classes and making only
external types like `ObjectMapper` a `String`. But `SalesforceEndpointConfig`
has its own `ObjectMapper` field, so walking into `config` still reaches the
failing path. Spring Boot has no per-field "don't walk into this" switch for
non-`java.*` types. Directions worth prototyping:
- keep the real types, but stop the generated configuration class from
exposing external, non-bean types (e.g. Jackson's) as walkable JavaBean
properties, including inside nested config classes, while still accepting
`#bean:` references;
- or limit the `String` mapping to options whose type is not a Camel
configuration class, and handle external-typed fields inside nested config
classes separately in the generator.
Both need checking against the existing binding tests (`#bean:` references,
nested `config.*`/`http-configuration.*` keys) before they're a real proposal.
I'll share results once I have a prototype. In the meantime, do you know which
non-enumerable property source is registered where you see the failure? If it
comes from a specific library or setup, that's useful context for the fix and
its test.
<details><summary>Reproducer (no salesforce properties, one non-enumerable
source; uses the <code>App</code> config from the previous reproducer)</summary>
```java
package org.apache.camel.component.salesforce.springboot;
import org.junit.jupiter.api.Test;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.PropertySource;
/**
* No salesforce property set at all, but the environment has a
non-enumerable PropertySource (as e.g. vault / cloud
* config / custom resolvers register), so the Binder cannot rule out
descendants and walks into the ObjectMapper.
*/
public class SalesforceNonEnumerableSourceReproducerTest {
@Test
void startup() {
SpringApplicationBuilder b = new
SpringApplicationBuilder(SalesforceObjectMapperBindingReproducerTest.App.class)
.web(WebApplicationType.NONE)
.initializers(ctx ->
ctx.getEnvironment().getPropertySources().addLast(new
PropertySource<Object>("nonEnumerable") {
@Override
public Object getProperty(String name) {
return null;
}
}));
try (ConfigurableApplicationContext ctx = b.run()) {
System.out.println("REPRO2 -> STARTED");
} catch (Exception e) {
Throwable root = e;
while (root.getCause() != null) {
root = root.getCause();
}
String msg = String.valueOf(e.getMessage());
int i = msg.indexOf("Failed to bind properties under");
System.out.println("REPRO2 -> FAILED " + (i >= 0 ?
msg.substring(i, Math.min(msg.length(), i + 250)) : msg) + " | root: " + root);
}
}
}
```
</details>
_Claude Code on behalf of Croway_
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]