Skip to content

CAMEL-23686: Avoid creating empty headers map on read access#23784

Merged
gnodet merged 1 commit into
apache:mainfrom
gnodet:CAMEL-23686/lazy-headers
Jun 5, 2026
Merged

CAMEL-23686: Avoid creating empty headers map on read access#23784
gnodet merged 1 commit into
apache:mainfrom
gnodet:CAMEL-23686/lazy-headers

Conversation

@gnodet

@gnodet gnodet commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

CAMEL-23686

Summary

  • getHeader(), removeHeader(), and removeHeaders() no longer force creation of a CaseInsensitiveMap when headers haven't been set
  • Short-circuits with null return when headers == null and isPopulateHeadersSupported() is false
  • JMS-style messages with lazy header population (isPopulateHeadersSupported() == true) are preserved

Problem

Every getHeader(name) call on a message with no headers forced creation of a CaseInsensitiveMap (allocating 4 internal arrays, ~200 bytes) just to return null. This happened frequently in splitter/multicast sub-exchanges that never use headers.

Before:

public Object getHeader(String name) {
    if (headers == null) {
        headers = createHeaders();  // allocates empty CaseInsensitiveMap
    }
    if (!headers.isEmpty()) {       // always false for new map
        return headers.get(name);
    } else {
        return null;                // returns null anyway
    }
}

After:

public Object getHeader(String name) {
    if (headers == null) {
        if (!isPopulateHeadersSupported()) {
            return null;            // no allocation
        }
        headers = createHeaders();
    }
    return headers.get(name);
}

Benchmark results

Baseline route (timer → split(1000) → setBody, no header access):

  • 3,891 DefaultMessage instances but only 4 CaseInsensitiveMap — split children don't allocate maps at all
  • Without this fix: every sub-exchange would create an empty map on first getHeader() call

Pipeline route (with setHeader on every exchange):

  • 24% throughput increase measured by in-flight exchange count at snapshot time
  • Map ratio stays at 0.57 (expected — every exchange writes headers)

Test plan

  • DefaultMessageHeaderTest (38 tests) — pass, including lazy-populated headers test
  • DefaultMessageTest — pass

getHeader(), removeHeader(), and removeHeaders() were forcing creation
of a CaseInsensitiveMap (allocating 4 arrays) just to return null when
no headers had been set. Now short-circuits when headers is null and
isPopulateHeadersSupported() is false (DefaultMessage). JMS-style
messages with lazy header population are preserved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gnodet gnodet requested review from davsclaus and oscerd June 5, 2026 11:01
@github-actions

github-actions Bot commented Jun 5, 2026

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.

@github-actions github-actions Bot added the core label Jun 5, 2026
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • core/camel-support

ℹ️ Dependent modules were not tested because the total number of affected modules exceeded the threshold (50). Use the test-dependents label to force testing all dependents.


⚙️ View full build and test results

@apupier apupier 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.

astonishing improvements due to only empty Map instanciation removal

EDIT: I think it will be worthy to wait for Claus or Andrea feedback before merging as they might better know if there are corner cases

@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.

Clean optimization — eliminates unnecessary CaseInsensitiveMap allocation (~200 bytes) on read access when headers haven't been set. The isPopulateHeadersSupported() guard correctly preserves the JMS-style lazy population path. The DRY refactor of getHeader overloads delegating to the base method is a nice simplification (-37 net lines). CI is green.

This review does not replace specialized AI review tools (CodeRabbit, Sourcery) or static analysis (SonarCloud).

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

@gnodet gnodet merged commit e17f4a7 into apache:main Jun 5, 2026
6 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.

4 participants