Federico Mariani created CAMEL-24128:
----------------------------------------
Summary: camel-jpa: JpaPollingConsumer.receive() leaks an
EntityManager on every call
Key: CAMEL-24128
URL: https://issues.apache.org/jira/browse/CAMEL-24128
Project: Camel
Issue Type: Bug
Components: camel-jpa
Reporter: Federico Mariani
h3. Problem
{{JpaPollingConsumer.receive()}}
(components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaPollingConsumer.java:126-132)
resolves its {{EntityManager}} via:
{code:java}
final EntityManager entityManager = getTargetEntityManager(null,
entityManagerFactory,
getEndpoint().isUsePassedInEntityManager(),
getEndpoint().isSharedEntityManager(), true);
...
exchange.getIn().setHeader(JpaConstants.ENTITY_MANAGER, entityManager);
{code}
Because the {{exchange}} argument passed to
{{JpaHelper.getTargetEntityManager}} is {{null}},
{{JpaHelper.createEntityManager()}} (JpaHelper.java:101-119) takes the
{{exchange == null}} branch and skips registering a
{{JpaCloseEntityManagerOnCompletion}} synchronization -- that registration only
happens when an exchange is supplied.
{{JpaPollingConsumer}} itself never closes the {{EntityManager}} it created:
the only close call in {{receive()}} is on the {{PersistenceException}} catch
path (line 172). On the success path the EM is simply left open and referenced
only via the {{CamelEntityManager}} header on the returned exchange.
h3. Impact
Every successful call to a JPA polling consumer (e.g.
{{pollEnrich("jpa:...")}}, or a direct
{{PollingConsumer.receive()}}/{{receiveNoWait()}}/{{receive(timeout)}}) leaks
one {{EntityManager}}. Each leaked EM retains its persistence context (all
entities loaded during the query) until garbage collected, and depending on the
JPA provider / connection-release-mode may also pin a JDBC connection. In a
route that polls frequently this is a slow, silent resource leak.
h3. How to reproduce
# Configure a {{JpaComponent}} with a real {{EntityManagerFactory}}.
# Call {{context.createPollingConsumer("jpa:com.foo.MyEntity").receive()}} (or
use {{pollEnrich}}) repeatedly.
# Observe (e.g. via a custom {{EntityManagerFactory}} wrapper, or provider
statistics) that {{EntityManager#close()}} is never invoked for the EMs created
by {{receive()}} on the success path.
h3. Suggested fix
Pass the created {{exchange}} into {{getTargetEntityManager}} before calling it
(so the on-completion synchronization gets registered), or explicitly close the
EM (and remove the header) once the transaction/exchange life-cycle in
{{receive()}} completes.
Found during a broader camel-jpa code review (July 2026).
_Claude Code on behalf of Croway_
--
This message was sent by Atlassian Jira
(v8.20.10#820010)