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)

Reply via email to