Skip to content

[CXF-9204] Fix NPE in ClientImpl when failover retry fails#2939

Merged
gnodet merged 2 commits intoapache:mainfrom
gnodet:fix/CXF-9204-failover-npe
Mar 11, 2026
Merged

[CXF-9204] Fix NPE in ClientImpl when failover retry fails#2939
gnodet merged 2 commits intoapache:mainfrom
gnodet:fix/CXF-9204-failover-npe

Conversation

@gnodet
Copy link
Contributor

@gnodet gnodet commented Mar 10, 2026

Summary

  • Root cause: In FailoverTargetSelector.performFailover(), the exception on outMessage is unconditionally cleared before the retry attempt (outMessage.setContent(Exception.class, null)), but the catch block only conditionally restores it (if (outMessage.getContent(Exception.class) != null)). Since the exception was already cleared, this condition is always false, so the exception is never restored when a retry fails.
  • Effect: The fault observer in ClientImpl.doInvoke() sees no exception on the message and enters the "handle the right response" path, where exchange.getInMessage() returns null (the exchange was cleared by exchange.clear()), causing a NullPointerException.
  • Fix: Unconditionally restore the previous exception and exchange fault in the catch block of performFailover(). Also add a defensive null check for inMsg in ClientImpl's fault observer.

Changes

  1. FailoverTargetSelector.performFailover() — always restore the original exception when the retry fails
  2. ClientImpl.doInvoke() fault observer — add null guard for inMsg after failover
  3. New FailoverTargetSelectorTest — verifies exception restoration on failed retry

Test plan

  • New unit test FailoverTargetSelectorTest.testExceptionRestoredOnFailedRetry passes with fix, fails without
  • Existing core endpoint tests pass
  • Systests clustering FailoverTest (manual verification recommended)
  • Camel CXF FailOverFeatureTest.testPojo() should now pass (the original reproducer)

🤖 Generated with Claude Code

…ails

When a failover retry throws an exception, FailoverTargetSelector.performFailover
unconditionally clears the exception from the outMessage (line 198) before the retry,
but the catch block only conditionally restores it (checking if the content is non-null).
Since it was already cleared, the condition is always false, so the exception is never
restored. This causes the fault observer in ClientImpl.doInvoke to enter the "handle
success" path where it tries to read the inbound message (which is null after
exchange.clear()), resulting in a NullPointerException.

Fix: unconditionally restore the previous exception in the catch block of performFailover.
Also add a defensive null check for the inbound message in ClientImpl's fault observer
to prevent NPE even if other code paths lead to a null inbound message after failover.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Per review feedback: with the root cause fix in FailoverTargetSelector
(unconditional exception restoration), inMsg should always be available
in this code path. Keep it clean.
Copy link
Contributor

@ffang ffang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @gnodet !

@gnodet gnodet merged commit 10ee0f1 into apache:main Mar 11, 2026
4 of 6 checks passed
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.

3 participants