[ 
https://issues.apache.org/jira/browse/CAMEL-24128?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Claus Ibsen resolved CAMEL-24128.
---------------------------------
    Resolution: Fixed

> 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
>            Assignee: Claus Ibsen
>            Priority: Major
>             Fix For: 4.22.0
>
>
> 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