Hello Claus,
First of thanks for the quick response.
My colleague and I did some further analysis of the issue and we suspect that 
there are more facets to it. See below the analysis prepared with the 
assistance of AI.

-------------------------

# Camel issue draft

> File at https://github.com/apache/camel/issues (Apache Camel now uses GitHub 
> Issues).
> Components: camel-core (PollEnricher / PollDynamicAware), camel-file, 
> camel-ftp.

## Title
Dynamic `pollEnrich` `fileName` referencing an exchange property is silently 
ignored; result depends on `allowOptimisedComponents` (documented as an 
optimisation only)

## Affects version(s)
- Reproduced on **4.14.7**. Likely all versions since **4.11.0** (when 
`PollDynamicAware` was introduced — CAMEL-21733).
- Java 21.

## Summary
When `pollEnrich` uses a dynamic endpoint URI whose `fileName` references an 
**exchange
property**, the filter silently fails to match the intended file. The behaviour 
depends on
`allowOptimisedComponents` (default `true`), which is documented purely as a 
performance
optimisation — yet toggling it changes the **result**:

- `allowOptimisedComponents(true)` (default): the wrong file is picked, or none 
is found.
- `allowOptimisedComponents(false)`: works correctly.

The same `fileName` referencing a **header** works with the optimisation on 
(for the directory-LIST
path), but a header does **not** work on the FTP `useList=false` path. This 
asymmetry is undocumented
and the failure is silent (no warning/exception).

## Steps to reproduce (camel-file, no server needed)
Two files exist in `target/in`: `aaa.txt` and `wanted.txt`.

```java
from("direct:start")
    .setProperty("myFile", constant("wanted.txt"))
    // dynamic pollEnrich; fileName references an EXCHANGE PROPERTY
    .pollEnrich()
        
.simple("file:target/in?fileName=${exchangeProperty.myFile}&noop=true&idempotent=false")
        .cacheSize(1)
        .timeout(2000)
    .to("mock:result");
```

Send one message to `direct:start` and inspect the enriched body / 
`CamelFileName`.

## Expected
The enricher retrieves `wanted.txt` (the file named by the exchange property).

## Actual
- With `allowOptimisedComponents(true)` (default): the enricher retrieves 
**`aaa.txt`** (the first
  file returned by the directory scan) — the `fileName` filter is silently 
ignored.
- Change the expression to a **header** (`${header.myFile}` + 
`.setHeader("myFile", ...)`): now it
  correctly retrieves `wanted.txt` with the optimisation on.
- Add `.allowOptimisedComponents(false)`: the original **exchange-property** 
version also correctly
  retrieves `wanted.txt`.

### FTP variant (camel-ftp)
On the FTP consumer the failure mode also depends on `useList`:
- `useList=true`: as above, the first listed file is taken (filter ignored).
- `useList=false`: the poll finds no file at all and times out (`fileName` 
resolves empty), for
  **both** exchange-property and header expressions.

## Root cause (as read in 4.14.7 sources)
1. With the optimisation on, `GenericFilePollDynamicAware.resolveStaticUri()` 
re-injects the
   *original, unresolved* `fileName` expression into a shared static endpoint, 
so `fileName` is
   evaluated **lazily** by the consumer.
2. The consumer evaluates `fileName` against a **dummy** exchange:
   - `useList=true` → `GenericFileConsumer.isMatched()` →
     `GenericFileHelper.createDummy(endpoint, dynamic, file)`, which copies 
only **headers** and
     **variables** from the routing exchange — **not exchange properties**:
     ```java
     if (dynamic.getMessage().hasHeaders()) {
         MessageHelper.copyHeaders(dynamic.getMessage(), dummy.getMessage(), 
true);
         if (dynamic.hasVariables()) {
             dummy.getVariables().putAll(dynamic.getVariables());
         }
     }
     ```
     So `${exchangeProperty.X}` resolves to `null`. And because `isMatched` 
skips the `fileName`
     filter when it evaluates to `null` (`if (result != null) { if 
(!name.equals(result)) ... }`),
     **all** files match and the first is taken.
   - `useList=false` → `FtpConsumer.pollNamedFile()` evaluates the expression 
against
     `ExchangeHelper.getDummy(context)` — a **completely empty** exchange (no 
headers, variables, or
     properties) — so the name resolves empty and no file is polled.
3. With `allowOptimisedComponents(false)`, `PollEnricher` resolves the URI 
expression **eagerly
   against the routing exchange**, yielding a literal `fileName`, which works 
regardless of `useList`
   and regardless of whether the source was a header or a property.

## Why this is a bug (or at least should change)
1. `allowOptimisedComponents` is documented as an optimisation; 
enabling/disabling it should not
   change results. Here it silently changes correctness.
2. The optimised path **silently** produces a wrong result (wrong file, or no 
file) when the dynamic
   `fileName` references data it cannot resolve, instead of failing fast or 
falling back.
3. `createDummy` copies headers **and** variables but **not** exchange 
properties — an undocumented
   asymmetry. Separately, variables are only copied when 
`dynamic.getMessage().hasHeaders()` is true,
   which looks like a latent bug (a message with variables but no headers won't 
have its variables
   carried over).
4. `isMatched` treating an empty/`null` `fileName` as "match everything" turns 
a mis-resolved
   dynamic filename into "grab the first file", which is a surprising and 
dangerous default for a
   named-file poll.

## Suggested fix (any of)
- Include exchange properties in `GenericFileHelper.createDummy` carry-over 
(and fix the
  variables-only-if-headers nesting), so property-based `fileName` expressions 
behave like
  header-based ones.
- Or have `GenericFilePollDynamicAware` detect that the `fileName` expression 
references
  data it does not support on the lazy path (e.g. `${exchangeProperty...}`) and 
skip the
  optimisation for that endpoint (fall back to eager resolution) rather than 
silently no-op.
- At minimum: document the limitation (only headers/variables are supported in 
an optimised dynamic
  `pollEnrich` `fileName`) and log a WARN when a dynamic `fileName` resolves to 
empty/null on the
  poll path.

## Workaround
Set `.allowOptimisedComponents(false)` on the `pollEnrich`, which resolves the 
dynamic URI eagerly
against the routing exchange into a literal `fileName`.




Marcus Ionker
Senior Product Evangelist, Messaging


Fax: +49 89 1250400-1202
Mobile: +49 176 15528012
Email: [email protected]

--------------------------------------------------------------------

retarus GmbH. Aschauer Straße 30, 81549 München, Germany. 
https://www.retarus.com
   On 2026/07/21 14:01:54 Marcus Ionker wrote:
> Hello Apache Camel Developers,
>
> I suspect a bug GenericFilePollDynamicAware.resolveStaticUri. I did not find 
> an existing Jira ticket in ASF pertaining to this issue.I prepared the 
> following ticket, which contains a detailed description of the problem. I 
> hope I have not overlooked anything. My current workaround is to set 
> allowOptimisedComponents to false. Maybe the problem is related to the fact 
> that although the file endpoints (incl. ftp, fps and sftp) support 
> expressions in the URI, they are resolved with an empty dummy exchange, 
> rather than an actual exchange.
>
> Best Regads,
> Marcus Ionker
>
> ----------------------------
>
> Bug:
>
> GenericFilePollDynamicAware.resolveStaticUri incorrectly returns static URI 
> for dynamic fileNameexpressions, breaking PollEnricher optimization
>
> Description:
>
> There is an optimization regression in 
> org.apache.camel.component.file.GenericFilePollDynamicAware when used 
> alongside pollEnrich.
>
> When a pollEnrich endpoint contains a dynamic simple expression targeting a 
> parameter like fileName (e.g., in a File or FTP/SFTP endpoint), 
> GenericFilePollDynamicAware.resolveStaticUri fails to return null. Other 
> implementations of PollDynamicAwareSupport correctly yield null when a 
> component configuration is dynamic. Because it incorrectly evaluates to a 
> non-null static URI base, PollEnricher.java falls into an invalid 
> optimization block.
>
> Root Cause Analysis:
>
> GenericFilePollDynamicAware.resolveStaticUri is returning the resolved 
> dynamic URI instead of null
>
> if (fileName) {
>    Map<String, Object> params = entry.getProperties();
>    Map<String, Object> originalParams = 
> URISupport.parseQuery(URISupport.extractQuery(entry.getOriginalUri()));
>    compute(originalParams, PROP_FILE_NAME, params);
>    return asEndpointUri(exchange, uri, params);
> } else {
>    return uri;
> }
>
> Inside org.apache.camel.processor.PollEnricher.process(), the framework 
> attempts to evaluate and optimize the dynamic endpoint.
>
> try {
>    recipient = expression.evaluate(exchange, Object.class);
>    if (dynamicAware != null) {
>       // if its the same scheme as the pre-resolved dynamic aware then we can 
> optimise to use it
>       String originalUri = uri;
>       String uri = resolveUri(exchange, recipient);
>       String scheme = resolveScheme(exchange, uri);
>       if (dynamicAware.getScheme().equals(scheme)) {
>          PollDynamicAware.DynamicAwareEntry entry = 
> dynamicAware.prepare(exchange, uri, originalUri);
>          if (entry != null) {
>             staticUri = dynamicAware.resolveStaticUri(exchange, entry);
>             if (staticUri != null) {
>                if (LOG.isDebugEnabled()) {
>                   LOG.debug("Optimising poll via PollDynamicAware component: 
> {} to use static uri: {}", scheme,
>                      URISupport.sanitizeUri(staticUri));
>                }
>             }
>          }
>       }
>    }
> }...
>
>
> The Breakdown:
>
> - expression.evaluate(...) correctly resolves the dynamic endpoint.
>
> - However, because GenericFilePollDynamicAware.resolveStaticUri provides a 
> fallback staticUri string rather than null, PollEnricher assumes it can 
> safety optimize the consumer.
>
> - Instead of using the fully evaluated runtime endpoint string (recipient), 
> the route erroneously falls back to using the unparsed simple expression 
> itself as the literal target.
>
> - The FTP/File consumer subsequently tries to poll using the literal 
> expression text, resulting in a lookup failure because the string syntax does 
> not match an actual file name on the remote target file system.
>
> Current Workaround:
>
> Configuring the EIP via 
> .pollEnrich().simple("...").allowOptimisedComponents(false)bypasses 
> PollDynamicAware entirely, forcing Camel to correctly interpret the parsed 
> expression string on every exchange.
>
> Expected Fix:
>
> GenericFilePollDynamicAware.resolveStaticUri must return null if the URI is 
> dynamic to disable optimization, mirroring standard PollDynamicAwareSupport 
> behavior.

Attachment: smime.p7s
Description: S/MIME cryptographic signature

Reply via email to