Skip to content

CAMEL-26179: camel-milo - a milo-client write of a null body fails the exchange - #28514

Merged
davsclaus merged 2 commits into
apache:mainfrom
etzel-mes:fix/CAMEL-26179-milo-write-body
Oct 27, 2026
Merged

davsclaus merged 2 commits into
apache:mainfrom
etzel-mes:fix/CAMEL-26179-milo-write-body

Conversation

@etzel-mes

@etzel-mes etzel-mes commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Description

Items 1 and 2 of CAMEL-26179; item 3 (milo-server) is left to the reporter.

  1. A write of a body that the client cannot encode no longer drops the shared connection since CAMEL-25912 (CAMEL-25912: camel-milo - client producers no longer wait for the manager lock or rebuild the connection #28371): a failed producer request completes with its failure and does not rebuild the connection. This adds the regression test ProducerOnSharedConnectionTest.writeOfABodyThatCannotBeEncodedKeepsTheConnection: an awaited write of a Map body fails with UaSerializationException, the consumer of another node on the same connection receives only its next value (no Bad_NoCommunication, no resent value over 2 s), and a following write on the same client and session answers GOOD. It fails when outcomeOf calls handleConnectionFailure again as before CAMEL-25912.
  2. A milo-client write of a null body now fails the exchange with an InvalidPayloadException before anything is sent, awaited or not. Before, it wrote a null value, which the server refused with Bad_TypeMismatch, and an awaited write completed without an error and with that status as the body. A DataValue or Variant body still writes a null value explicitly (the exception hint says so). A method call with a null body is unchanged: it calls the method without input arguments, as documented. Test: WriteClientTest.testWriteNullBodyFails (awaited and not awaited), which fails on the unmodified code.

Docs: one paragraph in the milo-client component doc (and its catalog copy) and a section in the 4.24 upgrade guide.

Module -Psourcecheck verify with -Dsurefire.rerunFailingTestsCount=0: 128 tests, 0 failures, 1 skipped (as upstream); the two changed classes green in 3 further runs.

Part of CAMEL-26179: the issue stays open for item 3 (milo-server).

@davsclaus @oscerd

Target

  • I checked that the commit is targeting the correct branch (Camel 4 uses the main branch)

Tracking

  • If this is a large change, bug fix, or code improvement, I checked there is a JIRA issue filed for the change (usually before you start working on it).

Apache Camel coding standards and style

  • I checked that each commit in the pull request has a meaningful subject line and body.
  • I have run mvn clean install -DskipTests locally from root folder and I have committed all auto-generated changes.

AI-assisted contributions

  • If this PR includes AI-generated code, commits have proper co-authorship attribution (e.g., Co-authored-by trailers) and the PR description identifies the AI tool used.

Claude Code on behalf of etzel-mes

…e exchange

A milo-client producer wrote a null body as a null value, which the server
refused with Bad_TypeMismatch; an awaited write completed without an error
and with that status as the body. The producer now fails the exchange with an
InvalidPayloadException before writing, awaited or not. A DataValue or Variant
body still writes a null value explicitly. A method call with a null body is
unchanged: it calls the method without input arguments.

A write of a body that the client cannot encode, such as a Map, no longer
disposes the shared connection since CAMEL-25912; a regression test now
covers it: the exchange fails, and the consumer of another node on the same
connection reports no loss and keeps its session.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

@oscerd oscerd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good fix — failing fast with a clear InvalidPayloadException beats the old behaviour where a null-body write went through, the server refused it with Bad_TypeMismatch, and an awaited write "succeeded" with that status as the body (a confusing silent-ish failure).

The guard is correctly scoped: value == null && !read && this.methodId == null fails only a genuine write — a read (via CamelMiloNodeIds) needs no body, and a method call with a null body still calls the method with no input args (nicely called out in the upgrade guide). Hoisting the containsKey(HEADER_NODE_IDS) check into the read local also removes the duplicate test below it. InvalidPayloadException is the right type — users can still catch it via onException.

Tests cover it well: testWriteNullBodyFails is parameterized over awaited/non-awaited, and writeOfABodyThatCannotBeEncodedKeepsTheConnection additionally pins down that an un-encodable body (a Map → UaSerializationException) fails the exchange without tearing down the shared connection, and a later valid write on the same session still succeeds. JUnit assertions + MockEndpoint.assertIsSatisfied + @Timeout, consistent with the module. Docs and the upgrade-guide entry (with the DataValue/Variant escape hatch) are in place.

No blockers. CI hasn't run here (fork PR) — worth triggering before merge.

Claude Code on behalf of oscerd

@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for this. I checked that the new guard in MiloClientProducer.java:77 fails only a real write: reads via CamelMiloNodeIds and method calls are unchanged. A DataValue or Variant body still goes through MiloClientConnection.mapWriteValue untouched, so the documented way to write a null value works. The existing MiloClientProducerWriteFailureTest uses a non-null body and is unaffected. The catalog copy of the doc matches, and the upgrade-guide entry covers the change for both awaited and fire-and-forget writes. The two tests pin down the null-body failure and that a body the client cannot encode no longer rebuilds the shared connection.

Two small, non-blocking notes:

  • The description says "Fixes CAMEL-26179", but item 3 of that issue (milo-server) is not in this PR. We should keep the JIRA open for it, or move item 3 to its own issue, so merging this does not close it by accident.
  • Nit: in milo-client-component.adoc:174 (and the upgrade guide), it could help to name Variant.NULL_VALUE as the concrete way to write a null value.

LGTM once CI has run green.

Reviewed with Claude Code on behalf of davsclaus. This review was generated by an AI agent and may contain inaccuracies; please verify all suggestions before applying.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • catalog/camel-catalog
  • components/camel-milo
  • docs

🔁 1 test passed only after a retry on JDK 21 (1 retried attempt)

Recovered flaky tests on JDK 21 (1)
Module Test Failed attempts First failure
dsl/camel-jbang/camel-jbang-plugin-tui MonitorStartupTest.browserOnlyServesUntilTheServerStops 1 Connection refused (connect failed)

ℹ️ These tests did not fail the build. Surefire retried them and they passed.
Retries are enabled project-wide by surefire.rerunFailingTestsCount in parent/pom.xml.


🔬 Scalpel shadow comparison — Scalpel: 8 of 708 tested, 25 compile-only — current: 8 all tested

Maveniverse Scalpel detected 8 affected modules (current approach: 8).

Skip-tests mode would test 8 modules (3 direct + 7 downstream), skip tests for 25 (generated code, meta-modules)

Modules Scalpel would test (8)
  • camel-jbang-mcp ← downstream of org.apache.camel:camel-catalog
  • camel-jbang-plugin-mcp ← downstream of org.apache.camel:camel-jbang-core
  • camel-jbang-plugin-tui ← downstream of org.apache.camel:camel-catalog
  • camel-jbang-plugin-validate ← downstream of org.apache.camel:camel-yaml-dsl-validator
  • camel-launcher-container ← downstream of org.apache.camel:camel-launcher
  • camel-milo ← components/camel-milo/src/main/docs/milo-client-component.adoc, components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientProducer.java, components/camel-milo/src/test/java/org/apache/camel/component/milo/ProducerOnSharedConnectionTest.java, components/camel-milo/src/test/java/org/apache/camel/component/milo/WriteClientTest.java
  • camel-yaml-dsl-validator ← downstream of org.apache.camel:camel-catalog
  • camel-yaml-dsl-validator-maven-plugin ← downstream of org.apache.camel:camel-yaml-dsl-validator
Modules with tests skipped (25)
  • apache-camel
  • camel-allcomponents
  • camel-catalog-console
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • dummy-component

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

All tested modules (35 modules, 5m 59s total)

Total reactor time: 5m 59s

Module Duration Status
Camel :: Launcher 54.1s SUCCESS
Camel :: JBang :: MCP 46.9s SUCCESS
Camel :: JBang :: Plugin :: Validate 28.9s SUCCESS
Camel :: YAML DSL :: Validator 28.1s SUCCESS
Camel :: Catalog :: Camel Catalog 24.6s SUCCESS
Camel :: YAML DSL 24.3s SUCCESS
Camel :: Component DSL 20.5s SUCCESS
Camel :: Kamelet Main 19.9s SUCCESS
Camel :: JBang :: Plugin :: Testing 18.8s SUCCESS
Camel :: JBang :: Plugin :: Kubernetes 16.7s SUCCESS
Camel :: Docs 14.9s SUCCESS
Camel :: YAML DSL :: Deserializers 8.4s SUCCESS
Camel :: Catalog :: Camel Report Maven Plugin 7.9s SUCCESS
Camel :: Catalog :: Camel Route Parser 7.7s SUCCESS
Camel :: Assembly 6.2s SUCCESS
Camel :: YAML DSL :: Validator Maven Plugin 5.3s SUCCESS
Camel :: All Components Sync point 5.2s SUCCESS
Camel :: YAML DSL :: Maven Plugins 3.5s SUCCESS
Camel :: Catalog :: Maven 3.4s SUCCESS
Camel :: Catalog :: Suggest (deprecated) 2.4s SUCCESS
Camel :: Coverage 1.5s SUCCESS
Camel :: Catalog :: Dummy Component 1.4s SUCCESS
Camel :: JBang :: Plugin :: Edit 1.3s SUCCESS
Camel :: JBang :: Integration tests 1.3s SUCCESS
Camel :: JBang :: Plugin :: MCP 1.2s SUCCESS
Camel :: JBang :: Plugin :: Generate 1.2s SUCCESS
Camel :: Catalog :: Console 0.9s SUCCESS
Camel :: Launcher :: Container 0.9s SUCCESS
Camel :: Endpoint DSL :: Support 0.8s SUCCESS
Camel :: JBang :: Main 0.7s SUCCESS
Camel :: Endpoint DSL n/a
Camel :: Integration Tests n/a
Camel :: JBang :: Core n/a
Camel :: JBang :: Plugin :: TUI n/a
Camel :: Milo n/a

Top 20 slowest modules:

  • Camel :: Launcher (54.1s)
  • Camel :: JBang :: MCP (46.9s)
  • Camel :: JBang :: Plugin :: Validate (28.9s)
  • Camel :: YAML DSL :: Validator (28.1s)
  • Camel :: Catalog :: Camel Catalog (24.6s)
  • Camel :: YAML DSL (24.3s)
  • Camel :: Component DSL (20.5s)
  • Camel :: Kamelet Main (19.9s)
  • Camel :: JBang :: Plugin :: Testing (18.8s)
  • Camel :: JBang :: Plugin :: Kubernetes (16.7s)
  • Camel :: Docs (14.9s)
  • Camel :: YAML DSL :: Deserializers (8.4s)
  • Camel :: Catalog :: Camel Report Maven Plugin (7.9s)
  • Camel :: Catalog :: Camel Route Parser (7.7s)
  • Camel :: Assembly (6.2s)
  • Camel :: YAML DSL :: Validator Maven Plugin (5.3s)
  • Camel :: All Components Sync point (5.2s)
  • Camel :: YAML DSL :: Maven Plugins (3.5s)
  • Camel :: Catalog :: Maven (3.4s)
  • Camel :: Catalog :: Suggest (deprecated) (2.4s)

⚙️ View full build and test results

@davsclaus

Copy link
Copy Markdown
Contributor

@etzel-mes CI is green now, thanks. Could you look at the two notes from my review before we merge?

  1. The description still ends with "Fixes CAMEL-26179", but item 3 (milo-server) is not in this PR. Please drop the "Fixes" line, or move item 3 to its own JIRA, so merging this does not close the issue by accident.
  2. Nit: in milo-client-component.adoc (and the upgrade guide), name Variant.NULL_VALUE as the concrete way to write a null value.

Claude Code on behalf of davsclaus

… a null value

Review note from davsclaus: the component doc, the 4.24 upgrade guide and
the InvalidPayloadException hint now name Variant.NULL_VALUE instead of a
generic DataValue or Variant body.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@etzel-mes

Copy link
Copy Markdown
Contributor Author

Thanks @davsclaus, both notes are addressed:

  • The description no longer says "Fixes CAMEL-26179". It now says this PR is part of the issue, which stays open for item 3 (milo-server).
  • The component doc (and its catalog copy), the 4.24 upgrade guide and the InvalidPayloadException hint now name Variant.NULL_VALUE as the way to write a null value.

The new commit only changes these wording lines. I ran the module with -Psourcecheck verify on current main plus this PR, including CAMEL-26112: 130 tests, 0 failures, 1 skipped (as upstream).

Claude Code on behalf of etzel-mes

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Guard logic is correctly scoped to the write path only (value == null && !read && this.methodId == null) — reads via CamelMiloNodeIds don't use the body, method calls with null still invoke the method with no arguments, and DataValue/Variant bodies carrying a null value pass through unchanged (the value variable is not null in those cases). The 4-arg InvalidPayloadException constructor produces a clear, actionable message.

Pre-fix test validation (static trace): testWriteNullBodyFails asserts InvalidPayloadException — without the guard, the write reaches the server which rejects it with Bad_TypeMismatch, a different exception type, so the test is correctly red before the fix. writeOfABodyThatCannotBeEncodedKeepsTheConnection pins the CAMEL-25912 regression — without that fix the shared connection is rebuilt, the consumer receives an error event, and the one-message assertion fails.

Docs and upgrade guide are complete: Variant.NULL_VALUE / DataValue escape hatch documented in both the component page (and its catalog copy) and the 4.24 upgrade guide.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@davsclaus davsclaus added this to the 4.24.0 milestone Oct 27, 2026
@davsclaus davsclaus added the bug Something isn't working label Oct 27, 2026
@gnodet

gnodet commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

🔄 Backport Bot

This bugfix targets main and may need porting to:

  • camel-4.22.x

Add a port/<branch> label to automatically create a backport PR for that branch.

Backport PRs will be created automatically on merge (once a port label is added). Comment /port to create them immediately.

ℹ️ If you push additional commits after /port, use /port again to update the port PRs.

@davsclaus
davsclaus merged commit 1c702a1 into apache:main Oct 27, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants