Skip to content

[backport camel-spring-boot-4.22.x] CAMEL-24501: Report starter configuration options that cannot be bound - #1960

Merged
Croway merged 3 commits into
apache:camel-spring-boot-4.22.xfrom
Croway:backport/1935-to-camel-spring-boot-4.22.x
Sep 9, 2026
Merged

Croway merged 3 commits into
apache:camel-spring-boot-4.22.xfrom
Croway:backport/1935-to-camel-spring-boot-4.22.x

Conversation

@Croway

@Croway Croway commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Cherry-pick of #1935 onto camel-spring-boot-4.22.x.

Original PR: #1935CAMEL-24501: Report starter configuration options that cannot be bound
JIRA: CAMEL-24501

What it fixes

SpringBootAutoConfigurationMojo generates two pieces of binding code into every starter, and both used to discard configuration silently instead of reporting it — so an option that never took effect looked identical to one that did.

First, generated converters for complex (object) type options bound via a bean reference (e.g. camel.component.http.ssl-context-parameters = #bean:mySslCtx) used to return null whenever the reference couldn't be resolved — a typo'd bean id, or a target type the converter didn't know about. The generated convert() body now delegates to a new BeanReferenceHelper, which resolves #bean:myBean / #myBean / plain myBean / #autowired / #type:fqn, and throws IllegalArgumentException naming the value, target type and configuration prefix when it can't resolve. This strict behaviour is scoped to classes that are Camel's own (under org.apache.camel, or @ConfigurationProperties with a camel. prefix) — a third-party class such as javax.net.ssl.HostnameVerifier that happens to share a type with a starter keeps its pre-existing lenient behaviour, so an unrelated application property can't be made to fail binding by a starter on the classpath.

Second, generated customizers no longer drop an option that can't be set on the target bean. They now call a new CamelPropertiesHelper.copyConfigurationProperties, which strips enabled/customizer before binding, fails with IllegalArgumentException when an option the application explicitly configured can't be set, and only logs at DEBUG when the option that can't be set merely carries its catalog default (not a hard failure — catalog defaults are currently materialized as field initializers regardless of whether the application configured them, which is item 2 of the ticket and a separate, still-open follow-up, deliberately not included in this backport).

Behavior change and opt-out

An application that configures an option which can never actually take effect now fails at startup, naming the option, instead of silently starting up with that option dropped. camel.springboot.lenient-configuration-binding=true restores the previous tolerant behaviour, logging at WARN instead of failing.

Verification on this branch

  • Cherry-picked all 3 commits (b689587a76a, 2e48974f6bf, e59029e0182) in order with git cherry-pick -x. This did not apply cleanly against the current camel-spring-boot-4.22.x tip:
    • The "Regen" commit conflicted on 8 generated files belonging to starters that don't exist on this branch at all (camel-hivemq-starter, camel-python3-starter, camel-quickjs-starter, camel-rest-postman-starter, camel-toon-starter, camel-ubl-starter — added to main after this branch diverged). Resolved by removing those files, matching this branch's (correct) deletion of them.
    • The follow-up commit conflicted in core/camel-spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json: the new camel.springboot.lenient-configuration-binding entry was inserted next to a camel.vault.ignore-resolution-failures entry that exists on main (from an unrelated, unbackported change) but not on this branch. Resolved by keeping only the lenient-configuration-binding entry that belongs to this ticket.
  • core/camel-spring-boot test suite: mvn verify — 164 unit tests + 2 integration tests, all passed, including the new BeanReferenceHelperTest (22 tests) and CamelPropertiesHelperLenientBindingTest.
  • tooling/camel-spring-boot-generator-maven-plugin test suite: mvn verify — 9 tests, all passed, including the new SpringBootAutoConfigurationMojoTest.
  • Spot-check: camel-http-starter builds cleanly against the regenerated code, and its new HttpComponentBeanReferenceBindingTest (6 tests) passes, exercising the fix end-to-end (unresolvable bean reference on a Camel property fails with IllegalArgumentException; an unrelated third-party property type keeps its lenient behaviour).
  • Local build used a temporary repoint of the SNAPSHOT parent/camel-version/BOM entries to the released 4.22.0 (this branch's SNAPSHOT parent isn't resolvable from a fresh checkout); those edits were reverted before pushing — the pushed branch contains only the 3 cherry-picked commits' real changes.

Claude Code on behalf of Federico Mariani

Croway and others added 3 commits September 9, 2026 12:37
The generated starter code discarded configuration in two places, in both
cases without a log line, so an option that never took effect looked exactly
like one that did.

The generated *ComponentConverter, *DataFormatConverter and *LanguageConverter
classes resolve the bean reference that an option of a complex (object) type is
configured with. They returned null for any value that did not start with #,
and for a value naming a bean that does not exist, so a typo in the bean id
produced a component with the option unset. The generated convert() body now
delegates to the new BeanReferenceHelper, which resolves #bean:id, #id, a plain
bean id, #autowired and #type:fqn, and throws IllegalArgumentException naming
the value, the target type and the configuration prefix when the value cannot
be resolved. This also drops the per-type switch, which returned null for a
target type it did not list.

The generated customizers copied the whole configuration onto the target with
CamelPropertiesHelper.copyProperties, which binds with failIfNotSet=false, so
an option with no matching setter on the target was dropped. The same mojo
emits failIfNotSet=true for camel.rest.*, so the two disagreed. The customizers
now call the new CamelPropertiesHelper.copyConfigurationProperties, which:

- removes the options owned by the auto configuration layer itself (enabled and
  customizer) before binding, as they are not options on the Camel target;
- fails with IllegalArgumentException when an option the application configured
  itself cannot be set;
- logs at DEBUG when an option that only carries its catalog default cannot be
  set, since the target keeps its own default and there is nothing the
  application can do about it.

Telling the two apart uses the Spring ConfigurationPropertySources, so a
catalog default that has never been bindable (camel.language.simple.trim, for
example, which is an option of the expression model rather than of
SimpleLanguage) does not turn into a startup failure for every application.
Blanket strict binding needs the catalog defaults to stop being materialised as
field initializers on the configuration classes first, which is left for a
follow-up.

camel.springboot.lenient-configuration-binding=true logs an explicitly
configured option at WARN and continues, instead of failing.

The language converter template also referenced an applicationContext field it
did not declare; no starter currently generates a language converter, so this
was latent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit b689587)
Regenerated the starters with the updated generator plugin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit 2e48974)
…ion binding

Review follow-up on the previous commit.

The generated converters are registered with @ConfigurationPropertiesBinding, so
they take part in every @ConfigurationProperties binding in the application, not
only in Camel's own. Failing closed unconditionally therefore turned an unrelated
application property of a type a starter registers for, such as
javax.net.ssl.HostnameVerifier, into a startup failure that talks about
camel.component.*.

BeanReferenceHelper now resolves the class being bound from
TypeDescriptor.getSource(), which Spring Boot's binder fills with the setter's
MethodParameter (or the Field for field access), and only applies the strict
behaviour when that class is Camel's own: under org.apache.camel, or annotated
with @ConfigurationProperties for a camel. prefix. Any other class keeps the
behaviour it had before, and an unrecognised source counts as Camel's own so that
Camel's own binding is never weakened.

This is decided in convert() rather than in ConditionalGenericConverter.matches:
GenericConversionService caches the converter it picked per source/target
TypeDescriptor pair and TypeDescriptor.equals ignores the source, so matches is
consulted once for the first class bound and the answer reused for every other
class with a field of the same type. A conditional converter would therefore be
order dependent, and in the bad order it would report a missing converter for a
valid Camel property.

Also from the review:

- #type: now checks that the bean found by type is assignable to the option type,
  instead of leaving a ClassCastException for the binder to hit later.
- camel.springboot.lenient-configuration-binding is read from the Spring
  Environment rather than from Camel's PropertiesComponent, and is declared in
  additional-spring-configuration-metadata.json so it shows up in IDE completion.
- The mojo fails the build if a catalog option is ever named enabled or
  customizer, since the customizers strip those before binding. No component,
  data format or language declares one today.
- The error message names the target class and points out that the option may be
  listed in the starter documentation, which is generated from the catalog rather
  than from that class, and so may never have taken effect.
- isExplicitlyConfigured logs at WARN when it cannot decide, as returning false
  there downgrades a hard error to an ignored option.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit e59029e)
@Croway
Croway requested review from davsclaus and oscerd September 9, 2026 10:46
@Croway
Croway merged commit e661e13 into apache:camel-spring-boot-4.22.x Sep 9, 2026
2 checks passed
@Croway
Croway deleted the backport/1935-to-camel-spring-boot-4.22.x branch September 9, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant