Skip to content

Conversation

@gnodet
Copy link
Contributor

@gnodet gnodet commented Jan 7, 2026

Summary

This PR introduces a new ContextValue abstraction that provides a unified API for thread-scoped data sharing, with implementations using either ThreadLocal (JDK 17+) or ScopedValue (JDK 25+ with virtual threads).

Related to: https://issues.apache.org/jira/browse/CAMEL-20199

Key Changes

New ContextValue API

  • Add ContextValue interface in camel-util with factory methods for creating context values and executing operations within scoped contexts
  • Add ContextValueFactory with ThreadLocal implementation for base JDK
  • Add Java 25 multi-release JAR variant using ScopedValue when available and virtual threads are enabled

API Improvements in ExtendedCamelContext

  • Add new scoped API methods with cleaner naming:
    • setupRoutes(Runnable) and setupRoutes(Callable<T>)
    • createRoute(String, Runnable) and createRoute(String, Callable<T>)
    • createProcessor(String, Runnable) and createProcessor(String, Callable<T>)
  • Deprecate the old boolean/void signaling methods (setupRoutes(boolean), createRoute(String), createProcessor(String))

Deprecations

  • Deprecate NamedThreadLocal in favor of ContextValue.newThreadLocal()

Implementation Updates

  • Update DefaultCamelContextExtension to use ContextValue.where() for scoped execution
  • Update DefaultReactiveExecutor to use ContextValue instead of NamedThreadLocal
  • Simplify Worker class by removing cached stats field (now reads statisticsEnabled directly)

Benefits

The ContextValue abstraction allows Camel to leverage ScopedValue on JDK 25+ when virtual threads are enabled, providing:

  • Better performance characteristics for virtual thread workloads (no pinning)
  • Immutable, inheritable values that work well with structured concurrency
  • Backward compatibility with ThreadLocal on older JDK versions

Documentation

Added documentation to ContextValue explaining that ThreadLocal variants should hold lightweight objects to avoid memory leaks with pooled threads.

This commit introduces a new ContextValue abstraction that provides a unified
API for thread-scoped data sharing, with implementations using either
ThreadLocal (JDK 17+) or ScopedValue (JDK 25+ with virtual threads).

Key changes:

- Add ContextValue interface in camel-util with factory methods for creating
  context values and executing operations within scoped contexts
- Add ContextValueFactory with ThreadLocal implementation for base JDK
- Add Java 25 multi-release JAR variant using ScopedValue when available
- Deprecate NamedThreadLocal in favor of ContextValue.newThreadLocal()
- Add new scoped API methods to ExtendedCamelContext:
  - setupRoutes(Runnable) and setupRoutes(Callable)
  - createRoute(String, Runnable) and createRoute(String, Callable)
  - createProcessor(String, Runnable) and createProcessor(String, Callable)
- Deprecate the old boolean/void signaling methods (setupRoutes(boolean),
  createRoute(String), createProcessor(String))
- Update DefaultCamelContextExtension to use ContextValue.where() for scoped
  execution, enabling proper ScopedValue support on virtual threads
- Update DefaultReactiveExecutor to use ContextValue instead of NamedThreadLocal
- Simplify Worker class by removing cached stats field

The ContextValue abstraction allows Camel to leverage ScopedValue on JDK 25+
when virtual threads are enabled, providing better performance characteristics
for virtual thread workloads while maintaining backward compatibility with
ThreadLocal on older JDK versions.

Documentation added to ContextValue explaining that ThreadLocal variants should
hold lightweight objects to avoid memory leaks with pooled threads.
@github-actions
Copy link
Contributor

github-actions bot commented Jan 7, 2026

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

  • You can label PRs using build-all, build-dependents, 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.

Add two disabled load test classes that can be run manually to compare
performance between platform threads and virtual threads:

- VirtualThreadsLoadTest: Uses SEDA with concurrent consumers to test
  throughput with simulated I/O delays
- VirtualThreadsWithThreadsDSLLoadTest: Uses threads() DSL to exercise
  the ContextValue/ScopedValue code paths

Tests are disabled by default and configurable via system properties:
- loadtest.messages: Number of messages to process (default: 5000)
- loadtest.producers: Number of producer threads (default: 50)
- loadtest.consumers: Number of concurrent consumers (default: 100)
- loadtest.delay: Simulated I/O delay in ms (default: 5-10)

Run with:
  mvn test -Dtest=VirtualThreadsLoadTest \
    -Djunit.jupiter.conditions.deactivate='org.junit.*DisabledCondition' \
    -Dcamel.threads.virtual.enabled=true
@davsclaus
Copy link
Contributor

I think its best to wait this kind of changes in core until after 4.18 LTS next month. Then we have an open path to do more bigger changes leading up for SB v4, Jackson 3, JUnit 6, Java 25 and other tasks that are more impactful.

Camel 4.18 LTS is expected to be similar to 4.14.x but as the last release supporting SB 3 that end users can use as a stable place.

Extract template method hooks in SedaConsumer to allow subclasses to
customize polling behavior without duplicating the entire doRun() loop:

- beforePoll(): Called before polling, returns true to proceed or false
  to skip this iteration. Allows acquiring resources like permits.

- afterPollEmpty(): Called when poll returns no message. Allows
  releasing resources.

- processPolledExchange(Exchange): Processes the polled exchange.
  Default is inline processing; can be overridden to dispatch to
  another thread.

Also made these methods protected for subclass access:
- createExecutor(int poolSize): Creates the executor service
- setupTasks(): Sets up thread pool and tasks
- shutdownExecutor(): Shuts down executors
- isShutdownPending()/setShutdownPending(): Access shutdown state
- pollTimeout field: Made protected

ThreadPerTaskSedaConsumer now simply overrides these hooks instead of
duplicating the entire polling loop, reducing code from 223 to 158 lines
and improving maintainability.
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