Skip to content

GH-3203 Skip standalone bindings in supplierInitializer to avoid ClassCastException for output-only bindings#3204

Closed
seonwooj0810 wants to merge 1 commit into
spring-cloud:mainfrom
seonwooj0810:fix/issue-3203-output-only-binding-supplier-cce
Closed

GH-3203 Skip standalone bindings in supplierInitializer to avoid ClassCastException for output-only bindings#3204
seonwooj0810 wants to merge 1 commit into
spring-cloud:mainfrom
seonwooj0810:fix/issue-3203-output-only-binding-supplier-cce

Conversation

@seonwooj0810

Copy link
Copy Markdown
Contributor

Fixes #3203

Problem

After upgrading to 5.0.2, an output-only binding (e.g. spring.cloud.stream.output-bindings=testOutput) with no backing Supplier/Function bean intermittently fails on startup with:

java.lang.ClassCastException: class ...DirectWithAttributesChannel$$SpringCGLIB$$0 cannot be cast to class java.util.function.Supplier
	at ...SimpleFunctionRegistry$FunctionInvocationWrapper.doApply(SimpleFunctionRegistry.java:826)
	...
	at ...AbstractPollingEndpoint.pollForMessage

Root cause

Commit f042adb (GH-3166) changed BindableFunctionProxyFactory.getFunctionDefinition() to always return the function definition instead of isFunctionExist() ? functionDefinition : null:

public String getFunctionDefinition() {
    //return this.isFunctionExist() ? this.functionDefinition : null;
    return this.functionDefinition;
}

For a standalone output binding the "function definition" is the binding name, so supplierInitializer now does functionCatalog.lookup("testOutput"), which resolves the plain output channel bean. isSupplier() reports true, a SourcePollingChannelAdapter is built on top of the channel, and the first poll casts the channel to SupplierClassCastException. In 5.0.1 getFunctionDefinition() returned null for these bindings, so lookup(null) returned null and they were skipped.

Fix

Guard the supplier initialization loop with proxyFactory.isFunctionExist(). Standalone bindings are constructed with functionExist == false (see createStandAloneBindingsIfNecessary, where new BindableFunctionProxyFactory(..., sourceFunc != null) and sourceFunc == null for a binding without a backing function), so they are now skipped — exactly the 5.0.1 behavior. Declared functions are constructed with functionExist == true, so real suppliers are unaffected. The change is local to supplierInitializer and does not revert the getFunctionDefinition() change that GH-3166 relies on for dynamic binding creation.

Test evidence

Added OutputOnlyBindingSupplierTests which starts an app with an output-only binding and asserts no SourcePollingChannelAdapter is created.

  • Without the fix the test fails: Expecting empty but was: {"testOutput_spca"=bean 'testOutput_spca'} (the SPCA that triggers the CCE on poll).
  • With the fix the test passes.
  • Full core/spring-cloud-stream module test suite is green: Tests run: 32, Failures: 0, Errors: 0, Skipped: 1 (pre-existing skip).

Verification done: (1) no in-flight PR (gh pr list search empty); (2) no self-claim on the issue; (4) grepped main and confirmed the commented-out guard in getFunctionDefinition() and the unguarded supplierInitializer; reproduced the SourcePollingChannelAdapter creation deterministically in a test on current main.

An output-only binding has no backing Supplier/Function bean, but since
spring-cloudgh-3166 stopped null-ing out BindableFunctionProxyFactory.getFunctionDefinition(),
its function definition is now the binding name. In supplierInitializer that
name is looked up in the function catalog, the plain output channel is wrapped,
isSupplier() reports true and a SourcePollingChannelAdapter is built on top of
the channel. The first poll then casts the channel to Supplier and throws a
ClassCastException, breaking output-only bindings that worked in 5.0.1.

Guard the supplier initialization with proxyFactory.isFunctionExist() so that
standalone bindings (functionExist == false) are skipped, restoring the 5.0.1
behavior without reverting the getFunctionDefinition() change required by spring-cloudgh-3166.

Signed-off-by: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com>
@olegz olegz closed this in e606dd0 Jun 19, 2026
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.

ClassCastException for output-only bindings in 5.0.2

1 participant