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.

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

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

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.
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
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.
Add a new 'virtualThreadPerTask' option to SedaEndpoint that enables
the ThreadPerTaskSedaConsumer. When enabled, spawns a new thread for
each message instead of using a fixed pool of consumer threads.

This model is optimized for virtual threads (JDK 21+) where thread
creation is very cheap, making it ideal for I/O-bound workloads. The
concurrentConsumers option becomes a limit on max concurrent tasks
(0 means unlimited).

Changes:
- Add virtualThreadPerTask property to SedaEndpoint with getter/setter
- Update createNewConsumer() to return ThreadPerTaskSedaConsumer when
  virtualThreadPerTask is enabled
- Update VirtualThreadsLoadTest to support testing with the new mode
- Add ThreadPerTaskSedaConsumerTest for unit testing the feature
- Regenerate endpoint configurers and metadata files
@gnodet gnodet force-pushed the context-value-scoped-value-support branch from 1cc11ea to f52c900 Compare January 12, 2026 14:50
Rethrow RuntimeException (and subclasses like IllegalArgumentException)
directly without wrapping. Only wrap checked exceptions in RuntimeException.

This fixes test failures where tests expected IllegalArgumentException
but were receiving RuntimeException wrapping IllegalArgumentException.
This commit adds a new documentation page covering virtual threads support
in Apache Camel, including:

- Introduction to virtual threads and why they matter for integration
- How to enable virtual threads globally in Camel
- Components with virtual thread support (SEDA, Jetty, Platform HTTP, etc.)
- SEDA deep dive with two execution models comparison (traditional vs
  virtualThreadPerTask) including a Mermaid diagram
- Backpressure and flow control mechanisms
- Context propagation with ContextValue (ThreadLocal vs ScopedValue)
- Best practices and performance considerations
- Complete code examples for common use cases

The article is added to the navigation under Architecture, after
Threading Model.
@github-actions github-actions bot added the docs label Jan 15, 2026
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