Skip to content

Use binary class name to resolve per-class HTTP interface fallbacks - #1715

Open
HDPark95 wants to merge 1 commit into
spring-cloud:mainfrom
HDPark95:fix/httpservice-fallback-nested-classname
Open

Use binary class name to resolve per-class HTTP interface fallbacks#1715
HDPark95 wants to merge 1 commit into
spring-cloud:mainfrom
HDPark95:fix/httpservice-fallback-nested-classname

Conversation

@HDPark95

@HDPark95 HDPark95 commented Aug 6, 2026

Copy link
Copy Markdown

What

CircuitBreakerRequestValueProcessor records the declaring class of the invoked @HttpExchange method under getCanonicalName(), but per-class fallbacks are registered with Class#getName() (the binary name) in CircuitBreakerConfigurerUtils.addFallbackEntries. getFallback then resolves the fallback by looking up that recorded value.

For a top-level interface both forms are identical, but for a nested @HttpExchange interface they differ: com.example.Outer.Inner (canonical) vs com.example.Outer$Inner (binary). The lookup key never matches the registration key, so the per-class fallback is silently skipped and the invocation falls through to the default fallback or, when none is registered, throws NoFallbackAvailableException.

Fix

Record the declaring class under getName() so the declaring-class lookup key matches the registration key. Top-level interfaces are unaffected because their canonical and binary names are identical.

This circuit-breaking-over-@HttpExchange support is new in 5.0.0 and unreleased, so aligning the stored attribute carries no compatibility concern.

Verification

  • Added shouldResolvePerClassFallbackForNestedServiceInterface to CircuitBreakerAdapterDecoratorTests, which drives the real CircuitBreakerRequestValueProcessor with a nested service interface and asserts the per-class fallback resolves.
  • With the fix reverted the new test fails with NoFallbackAvailableException; with the fix the whole test class passes (Tests run: 6, Failures: 0, Errors: 0).
  • ./mvnw -pl spring-cloud-commons test is green, including spring-javaformat and checkstyle validation.

CircuitBreakerRequestValueProcessor stored the declaring class under its
canonical name, while fallbacks are registered per service using
Class#getName() (the binary name). For a nested @HttpExchange interface
these two forms differ (Outer.Inner vs Outer$Inner), so the per-class
fallback was never matched and the invocation fell through to the default
fallback or NoFallbackAvailableException.

Store the binary name so the declaring-class lookup key matches the
registration key. Top-level interfaces are unaffected because their
canonical and binary names are identical.

Signed-off-by: HDPark95 <qkrgusen456@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants