This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 66d37e8b3887 CAMEL-24525: camel-google-firestore - bound the realtime 
change buffer
66d37e8b3887 is described below

commit 66d37e8b3887314331d7c853d37142757f9eaa18
Author: Andrea Cosentino <[email protected]>
AuthorDate: Sun Aug 30 13:48:24 2026 +0200

    CAMEL-24525: camel-google-firestore - bound the realtime change buffer
    
    GoogleFirestoreConsumer buffered document changes reported by the snapshot
    listener in an unbounded queue and drained it on the scheduled poll. The
    listener callback runs on a Firestore client thread and is never throttled
    by the route, so a collection changing faster than the route consumes it
    made the buffer grow without limit, holding full Exchange objects. It also
    kept growing while the route was suspended.
    
    A new maxPendingChanges consumer option bounds the buffer. The default is
    unbounded, so existing routes are unaffected. When a limit is reached, the
    oldest buffered change is discarded and a warning logged, leaving the route
    with the most recent state. Blocking the listener thread was deliberately
    not offered, since it belongs to the Firestore gRPC client and would stall
    the watch stream. The buffer is now a LinkedBlockingQueue for O(1) size and
    drainTo support.
    
    Closes #25898
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 .../camel/catalog/components/google-firestore.json |  50 +++++-----
 .../catalog/docs/google-firestore-component.adoc   |  12 +++
 .../GoogleFirestoreComponentConfigurer.java        |   6 ++
 .../GoogleFirestoreEndpointConfigurer.java         |   6 ++
 .../GoogleFirestoreEndpointUriFactory.java         |   3 +-
 .../google/firestore/google-firestore.json         |  50 +++++-----
 .../src/main/docs/google-firestore-component.adoc  |  12 +++
 .../firestore/GoogleFirestoreConfiguration.java    |  22 +++++
 .../google/firestore/GoogleFirestoreConsumer.java  |  68 +++++++++++--
 .../GoogleFirestoreConsumerBufferTest.java         | 106 +++++++++++++++++++++
 .../GoogleFirestoreComponentBuilderFactory.java    |  22 +++++
 .../dsl/GoogleFirestoreEndpointBuilderFactory.java |  42 ++++++++
 12 files changed, 342 insertions(+), 57 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/google-firestore.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/google-firestore.json
index 11f9b15d2195..05587d088f63 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/google-firestore.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/google-firestore.json
@@ -31,12 +31,13 @@
     "serviceAccountKey": { "index": 4, "kind": "property", "displayName": 
"Service Account Key", "group": "common", "label": "common", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "The Service account key 
that can be used as credentials for the Firestore cli [...]
     "bridgeErrorHandler": { "index": 5, "kind": "property", "displayName": 
"Bridge Error Handler", "group": "consumer", "label": "consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Allows for bridging the consumer to the Camel routing Error Handler, which 
mean any exceptions (if possible) occurred while the Camel consumer is trying 
to pickup incoming messages, or the like [...]
     "realtimeUpdates": { "index": 6, "kind": "property", "displayName": 
"Realtime Updates", "group": "consumer", "label": "consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, 
"configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "When true, the consumer 
will listen for real-time updates on th [...]
-    "documentId": { "index": 7, "kind": "property", "displayName": "Document 
Id", "group": "producer", "label": "producer", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "The document ID to use 
for document-specific operations" },
-    "lazyStartProducer": { "index": 8, "kind": "property", "displayName": 
"Lazy Start Producer", "group": "producer", "label": "producer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether the producer should be started lazy (on the first message). By 
starting lazy you can use this to allow CamelContext and routes to startup in 
situations where a producer may otherwise fail [...]
-    "operation": { "index": 9, "kind": "property", "displayName": "Operation", 
"group": "producer", "label": "producer", "required": false, "type": "enum", 
"javaType": 
"org.apache.camel.component.google.firestore.GoogleFirestoreOperations", 
"enum": [ "setDocument", "getDocumentById", "updateDocument", "deleteDocument", 
"queryCollection", "listDocuments", "listCollections", "createDocument" ], 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.came [...]
-    "autowiredEnabled": { "index": 10, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching  [...]
-    "healthCheckConsumerEnabled": { "index": 11, "kind": "property", 
"displayName": "Health Check Consumer Enabled", "group": "health", "label": 
"health", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Used for enabling or disabling all consumer based health checks 
from this component" },
-    "healthCheckProducerEnabled": { "index": 12, "kind": "property", 
"displayName": "Health Check Producer Enabled", "group": "health", "label": 
"health", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Used for enabling or disabling all producer based health checks 
from this component. Notice: Camel has by default disabled all producer based 
health-checks. You can turn on produce [...]
+    "maxPendingChanges": { "index": 7, "kind": "property", "displayName": "Max 
Pending Changes", "group": "consumer (advanced)", "label": "consumer,advanced", 
"required": false, "type": "integer", "javaType": "int", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": 0, "configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "Maximum number of 
realtime document changes bu [...]
+    "documentId": { "index": 8, "kind": "property", "displayName": "Document 
Id", "group": "producer", "label": "producer", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "The document ID to use 
for document-specific operations" },
+    "lazyStartProducer": { "index": 9, "kind": "property", "displayName": 
"Lazy Start Producer", "group": "producer", "label": "producer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether the producer should be started lazy (on the first message). By 
starting lazy you can use this to allow CamelContext and routes to startup in 
situations where a producer may otherwise fail [...]
+    "operation": { "index": 10, "kind": "property", "displayName": 
"Operation", "group": "producer", "label": "producer", "required": false, 
"type": "enum", "javaType": 
"org.apache.camel.component.google.firestore.GoogleFirestoreOperations", 
"enum": [ "setDocument", "getDocumentById", "updateDocument", "deleteDocument", 
"queryCollection", "listDocuments", "listCollections", "createDocument" ], 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.cam [...]
+    "autowiredEnabled": { "index": 11, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching  [...]
+    "healthCheckConsumerEnabled": { "index": 12, "kind": "property", 
"displayName": "Health Check Consumer Enabled", "group": "health", "label": 
"health", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Used for enabling or disabling all consumer based health checks 
from this component" },
+    "healthCheckProducerEnabled": { "index": 13, "kind": "property", 
"displayName": "Health Check Producer Enabled", "group": "health", "label": 
"health", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Used for enabling or disabling all producer based health checks 
from this component. Notice: Camel has by default disabled all producer based 
health-checks. You can turn on produce [...]
   },
   "headers": {
     "CamelGoogleFirestoreOperation": { "index": 0, "kind": "header", 
"displayName": "", "group": "producer", "label": "producer", "required": false, 
"javaType": 
"org.apache.camel.component.google.firestore.GoogleFirestoreOperations", 
"enum": [ "setDocument", "getDocumentById", "updateDocument", "deleteDocument", 
"queryCollection", "listDocuments", "listCollections", "createDocument" ], 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The op [...]
@@ -67,23 +68,24 @@
     "bridgeErrorHandler": { "index": 7, "kind": "parameter", "displayName": 
"Bridge Error Handler", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Allows for bridging the consumer to the 
Camel routing Error Handler, which mean any exceptions (if possible) occurred 
while the Camel consumer is trying to pickup incoming  [...]
     "exceptionHandler": { "index": 8, "kind": "parameter", "displayName": 
"Exception Handler", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "object", "javaType": 
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", 
"deprecated": false, "autowired": false, "secret": false, "description": "To 
let the consumer use a custom ExceptionHandler. Notice if the option 
bridgeErrorHandler is enabled then this option is not in use. By def [...]
     "exchangePattern": { "index": 9, "kind": "parameter", "displayName": 
"Exchange Pattern", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "enum", "javaType": 
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], 
"deprecated": false, "autowired": false, "secret": false, "description": "Sets 
the exchange pattern when the consumer creates an exchange." },
-    "pollStrategy": { "index": 10, "kind": "parameter", "displayName": "Poll 
Strategy", "group": "consumer (advanced)", "label": "consumer,advanced", 
"required": false, "type": "object", "javaType": 
"org.apache.camel.spi.PollingConsumerPollStrategy", "deprecated": false, 
"autowired": false, "secret": false, "description": "A pluggable 
org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your 
custom implementation to control error handling usually occurred during the po 
[...]
-    "documentId": { "index": 11, "kind": "parameter", "displayName": "Document 
Id", "group": "producer", "label": "producer", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "The document ID to use 
for document-specific operations" },
-    "operation": { "index": 12, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "producer", "required": false, 
"type": "enum", "javaType": 
"org.apache.camel.component.google.firestore.GoogleFirestoreOperations", 
"enum": [ "setDocument", "getDocumentById", "updateDocument", "deleteDocument", 
"queryCollection", "listDocuments", "listCollections", "createDocument" ], 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.ca [...]
-    "lazyStartProducer": { "index": 13, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produ [...]
-    "backoffErrorThreshold": { "index": 14, "kind": "parameter", 
"displayName": "Backoff Error Threshold", "group": "scheduler", "label": 
"consumer,scheduler", "required": false, "type": "integer", "javaType": "int", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
number of subsequent error polls (failed due some error) that should happen 
before the backoffMultipler should kick-in." },
-    "backoffIdleThreshold": { "index": 15, "kind": "parameter", "displayName": 
"Backoff Idle Threshold", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "integer", "javaType": "int", "deprecated": false, 
"autowired": false, "secret": false, "description": "The number of subsequent 
idle polls that should happen before the backoffMultipler should kick-in." },
-    "backoffMultiplier": { "index": 16, "kind": "parameter", "displayName": 
"Backoff Multiplier", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "integer", "javaType": "int", "deprecated": false, 
"autowired": false, "secret": false, "description": "To let the scheduled 
polling consumer backoff if there has been a number of subsequent idles\/errors 
in a row. The multiplier is then the number of polls that will be skipped 
before the next actual attempt is h [...]
-    "delay": { "index": 17, "kind": "parameter", "displayName": "Delay", 
"group": "scheduler", "label": "consumer,scheduler", "required": false, "type": 
"integer", "javaType": "long", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 500, "description": "Milliseconds before the 
next poll." },
-    "greedy": { "index": 18, "kind": "parameter", "displayName": "Greedy", 
"group": "scheduler", "label": "consumer,scheduler", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": false, "description": "If greedy is enabled, 
then the ScheduledPollConsumer will run immediately again, if the previous run 
polled 1 or more messages." },
-    "initialDelay": { "index": 19, "kind": "parameter", "displayName": 
"Initial Delay", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "integer", "javaType": "long", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": 1000, "description": 
"Milliseconds before the first poll starts." },
-    "repeatCount": { "index": 20, "kind": "parameter", "displayName": "Repeat 
Count", "group": "scheduler", "label": "consumer,scheduler", "required": false, 
"type": "integer", "javaType": "long", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 0, "description": "Specifies a maximum limit 
of number of fires. So if you set it to 1, the scheduler will only fire once. 
If you set it to 5, it will only fire five times. A value of zero or negative 
means fire forever." },
-    "runLoggingLevel": { "index": 21, "kind": "parameter", "displayName": "Run 
Logging Level", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "enum", "javaType": "org.apache.camel.LoggingLevel", 
"enum": [ "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF" ], "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": "TRACE", 
"description": "The consumer logs a start\/complete log line when it polls. 
This option allows you to configure the log [...]
-    "scheduledExecutorService": { "index": 22, "kind": "parameter", 
"displayName": "Scheduled Executor Service", "group": "scheduler", "label": 
"consumer,scheduler", "required": false, "type": "object", "javaType": 
"java.util.concurrent.ScheduledExecutorService", "deprecated": false, 
"autowired": false, "secret": false, "description": "Allows for configuring a 
custom\/shared thread pool to use for the consumer. By default each consumer 
has its own single threaded thread pool." },
-    "scheduler": { "index": 23, "kind": "parameter", "displayName": 
"Scheduler", "group": "scheduler", "label": "consumer,scheduler", "required": 
false, "type": "object", "javaType": "java.lang.Object", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": "none", "description": "To 
use a cron scheduler from either camel-spring or camel-quartz component. Use 
value spring or quartz for built in scheduler" },
-    "schedulerProperties": { "index": 24, "kind": "parameter", "displayName": 
"Scheduler Properties", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "object", "javaType": 
"java.util.Map<java.lang.String, java.lang.Object>", "prefix": "scheduler.", 
"multiValue": true, "deprecated": false, "autowired": false, "secret": false, 
"description": "To configure additional properties when using a custom 
scheduler or any of the Quartz, Spring based scheduler. This i [...]
-    "startScheduler": { "index": 25, "kind": "parameter", "displayName": 
"Start Scheduler", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "boolean", "javaType": "boolean", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Whether the scheduler should be auto started." },
-    "timeUnit": { "index": 26, "kind": "parameter", "displayName": "Time 
Unit", "group": "scheduler", "label": "consumer,scheduler", "required": false, 
"type": "enum", "javaType": "java.util.concurrent.TimeUnit", "enum": [ 
"NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS", "MINUTES", "HOURS", 
"DAYS" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "MILLISECONDS", "description": "Time unit for initialDelay and 
delay options." },
-    "useFixedDelay": { "index": 27, "kind": "parameter", "displayName": "Use 
Fixed Delay", "group": "scheduler", "label": "consumer,scheduler", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in 
JDK for details." }
+    "maxPendingChanges": { "index": 10, "kind": "parameter", "displayName": 
"Max Pending Changes", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "integer", "javaType": "int", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, 
"configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "Maximum number of 
realtime document changes  [...]
+    "pollStrategy": { "index": 11, "kind": "parameter", "displayName": "Poll 
Strategy", "group": "consumer (advanced)", "label": "consumer,advanced", 
"required": false, "type": "object", "javaType": 
"org.apache.camel.spi.PollingConsumerPollStrategy", "deprecated": false, 
"autowired": false, "secret": false, "description": "A pluggable 
org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your 
custom implementation to control error handling usually occurred during the po 
[...]
+    "documentId": { "index": 12, "kind": "parameter", "displayName": "Document 
Id", "group": "producer", "label": "producer", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "The document ID to use 
for document-specific operations" },
+    "operation": { "index": 13, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "producer", "required": false, 
"type": "enum", "javaType": 
"org.apache.camel.component.google.firestore.GoogleFirestoreOperations", 
"enum": [ "setDocument", "getDocumentById", "updateDocument", "deleteDocument", 
"queryCollection", "listDocuments", "listCollections", "createDocument" ], 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.ca [...]
+    "lazyStartProducer": { "index": 14, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produ [...]
+    "backoffErrorThreshold": { "index": 15, "kind": "parameter", 
"displayName": "Backoff Error Threshold", "group": "scheduler", "label": 
"consumer,scheduler", "required": false, "type": "integer", "javaType": "int", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
number of subsequent error polls (failed due some error) that should happen 
before the backoffMultipler should kick-in." },
+    "backoffIdleThreshold": { "index": 16, "kind": "parameter", "displayName": 
"Backoff Idle Threshold", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "integer", "javaType": "int", "deprecated": false, 
"autowired": false, "secret": false, "description": "The number of subsequent 
idle polls that should happen before the backoffMultipler should kick-in." },
+    "backoffMultiplier": { "index": 17, "kind": "parameter", "displayName": 
"Backoff Multiplier", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "integer", "javaType": "int", "deprecated": false, 
"autowired": false, "secret": false, "description": "To let the scheduled 
polling consumer backoff if there has been a number of subsequent idles\/errors 
in a row. The multiplier is then the number of polls that will be skipped 
before the next actual attempt is h [...]
+    "delay": { "index": 18, "kind": "parameter", "displayName": "Delay", 
"group": "scheduler", "label": "consumer,scheduler", "required": false, "type": 
"integer", "javaType": "long", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 500, "description": "Milliseconds before the 
next poll." },
+    "greedy": { "index": 19, "kind": "parameter", "displayName": "Greedy", 
"group": "scheduler", "label": "consumer,scheduler", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": false, "description": "If greedy is enabled, 
then the ScheduledPollConsumer will run immediately again, if the previous run 
polled 1 or more messages." },
+    "initialDelay": { "index": 20, "kind": "parameter", "displayName": 
"Initial Delay", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "integer", "javaType": "long", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": 1000, "description": 
"Milliseconds before the first poll starts." },
+    "repeatCount": { "index": 21, "kind": "parameter", "displayName": "Repeat 
Count", "group": "scheduler", "label": "consumer,scheduler", "required": false, 
"type": "integer", "javaType": "long", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 0, "description": "Specifies a maximum limit 
of number of fires. So if you set it to 1, the scheduler will only fire once. 
If you set it to 5, it will only fire five times. A value of zero or negative 
means fire forever." },
+    "runLoggingLevel": { "index": 22, "kind": "parameter", "displayName": "Run 
Logging Level", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "enum", "javaType": "org.apache.camel.LoggingLevel", 
"enum": [ "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF" ], "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": "TRACE", 
"description": "The consumer logs a start\/complete log line when it polls. 
This option allows you to configure the log [...]
+    "scheduledExecutorService": { "index": 23, "kind": "parameter", 
"displayName": "Scheduled Executor Service", "group": "scheduler", "label": 
"consumer,scheduler", "required": false, "type": "object", "javaType": 
"java.util.concurrent.ScheduledExecutorService", "deprecated": false, 
"autowired": false, "secret": false, "description": "Allows for configuring a 
custom\/shared thread pool to use for the consumer. By default each consumer 
has its own single threaded thread pool." },
+    "scheduler": { "index": 24, "kind": "parameter", "displayName": 
"Scheduler", "group": "scheduler", "label": "consumer,scheduler", "required": 
false, "type": "object", "javaType": "java.lang.Object", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": "none", "description": "To 
use a cron scheduler from either camel-spring or camel-quartz component. Use 
value spring or quartz for built in scheduler" },
+    "schedulerProperties": { "index": 25, "kind": "parameter", "displayName": 
"Scheduler Properties", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "object", "javaType": 
"java.util.Map<java.lang.String, java.lang.Object>", "prefix": "scheduler.", 
"multiValue": true, "deprecated": false, "autowired": false, "secret": false, 
"description": "To configure additional properties when using a custom 
scheduler or any of the Quartz, Spring based scheduler. This i [...]
+    "startScheduler": { "index": 26, "kind": "parameter", "displayName": 
"Start Scheduler", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "boolean", "javaType": "boolean", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Whether the scheduler should be auto started." },
+    "timeUnit": { "index": 27, "kind": "parameter", "displayName": "Time 
Unit", "group": "scheduler", "label": "consumer,scheduler", "required": false, 
"type": "enum", "javaType": "java.util.concurrent.TimeUnit", "enum": [ 
"NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS", "MINUTES", "HOURS", 
"DAYS" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "MILLISECONDS", "description": "Time unit for initialDelay and 
delay options." },
+    "useFixedDelay": { "index": 28, "kind": "parameter", "displayName": "Use 
Fixed Delay", "group": "scheduler", "label": "consumer,scheduler", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in 
JDK for details." }
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/google-firestore-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/google-firestore-component.adoc
index 3fc57d7b5684..62929b4ed958 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/google-firestore-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/google-firestore-component.adoc
@@ -243,6 +243,18 @@ YAML::
 ----
 ====
 
+In real-time mode the document changes reported by the listener are buffered 
until the next poll picks them up. That
+buffer is unbounded by default, so a collection changing faster than the route 
consumes it makes the buffer grow, and
+it keeps growing while the route is suspended. Set `maxPendingChanges` to 
bound it: when the buffer is full the oldest
+buffered change is discarded and a warning is logged, which leaves the route 
with the most recent state of the
+collection.
+
+[source,java]
+----
+from("google-firestore://myCollection?realtimeUpdates=true&maxPendingChanges=1000")
+    .to("log:changes");
+----
+
 === Advanced Component Configuration
 
 If you need more control over the `Firestore` client instance configuration, 
you can create your own instance and refer to it in your Camel google-firestore 
component configuration:
diff --git 
a/components/camel-google/camel-google-firestore/src/generated/java/org/apache/camel/component/google/firestore/GoogleFirestoreComponentConfigurer.java
 
b/components/camel-google/camel-google-firestore/src/generated/java/org/apache/camel/component/google/firestore/GoogleFirestoreComponentConfigurer.java
index c4316878327a..155cfd20a31e 100644
--- 
a/components/camel-google/camel-google-firestore/src/generated/java/org/apache/camel/component/google/firestore/GoogleFirestoreComponentConfigurer.java
+++ 
b/components/camel-google/camel-google-firestore/src/generated/java/org/apache/camel/component/google/firestore/GoogleFirestoreComponentConfigurer.java
@@ -47,6 +47,8 @@ public class GoogleFirestoreComponentConfigurer extends 
PropertyConfigurerSuppor
         case "healthCheckProducerEnabled": 
target.setHealthCheckProducerEnabled(property(camelContext, boolean.class, 
value)); return true;
         case "lazystartproducer":
         case "lazyStartProducer": 
target.setLazyStartProducer(property(camelContext, boolean.class, value)); 
return true;
+        case "maxpendingchanges":
+        case "maxPendingChanges": 
getOrCreateConfiguration(target).setMaxPendingChanges(property(camelContext, 
int.class, value)); return true;
         case "operation": 
getOrCreateConfiguration(target).setOperation(property(camelContext, 
org.apache.camel.component.google.firestore.GoogleFirestoreOperations.class, 
value)); return true;
         case "projectid":
         case "projectId": 
getOrCreateConfiguration(target).setProjectId(property(camelContext, 
java.lang.String.class, value)); return true;
@@ -83,6 +85,8 @@ public class GoogleFirestoreComponentConfigurer extends 
PropertyConfigurerSuppor
         case "healthCheckProducerEnabled": return boolean.class;
         case "lazystartproducer":
         case "lazyStartProducer": return boolean.class;
+        case "maxpendingchanges":
+        case "maxPendingChanges": return int.class;
         case "operation": return 
org.apache.camel.component.google.firestore.GoogleFirestoreOperations.class;
         case "projectid":
         case "projectId": return java.lang.String.class;
@@ -115,6 +119,8 @@ public class GoogleFirestoreComponentConfigurer extends 
PropertyConfigurerSuppor
         case "healthCheckProducerEnabled": return 
target.isHealthCheckProducerEnabled();
         case "lazystartproducer":
         case "lazyStartProducer": return target.isLazyStartProducer();
+        case "maxpendingchanges":
+        case "maxPendingChanges": return 
getOrCreateConfiguration(target).getMaxPendingChanges();
         case "operation": return 
getOrCreateConfiguration(target).getOperation();
         case "projectid":
         case "projectId": return 
getOrCreateConfiguration(target).getProjectId();
diff --git 
a/components/camel-google/camel-google-firestore/src/generated/java/org/apache/camel/component/google/firestore/GoogleFirestoreEndpointConfigurer.java
 
b/components/camel-google/camel-google-firestore/src/generated/java/org/apache/camel/component/google/firestore/GoogleFirestoreEndpointConfigurer.java
index f20599581ce5..dee730d2bb51 100644
--- 
a/components/camel-google/camel-google-firestore/src/generated/java/org/apache/camel/component/google/firestore/GoogleFirestoreEndpointConfigurer.java
+++ 
b/components/camel-google/camel-google-firestore/src/generated/java/org/apache/camel/component/google/firestore/GoogleFirestoreEndpointConfigurer.java
@@ -47,6 +47,8 @@ public class GoogleFirestoreEndpointConfigurer extends 
PropertyConfigurerSupport
         case "initialDelay": target.setInitialDelay(property(camelContext, 
long.class, value)); return true;
         case "lazystartproducer":
         case "lazyStartProducer": 
target.setLazyStartProducer(property(camelContext, boolean.class, value)); 
return true;
+        case "maxpendingchanges":
+        case "maxPendingChanges": 
target.getConfiguration().setMaxPendingChanges(property(camelContext, 
int.class, value)); return true;
         case "operation": 
target.getConfiguration().setOperation(property(camelContext, 
org.apache.camel.component.google.firestore.GoogleFirestoreOperations.class, 
value)); return true;
         case "pollstrategy":
         case "pollStrategy": target.setPollStrategy(property(camelContext, 
org.apache.camel.spi.PollingConsumerPollStrategy.class, value)); return true;
@@ -109,6 +111,8 @@ public class GoogleFirestoreEndpointConfigurer extends 
PropertyConfigurerSupport
         case "initialDelay": return long.class;
         case "lazystartproducer":
         case "lazyStartProducer": return boolean.class;
+        case "maxpendingchanges":
+        case "maxPendingChanges": return int.class;
         case "operation": return 
org.apache.camel.component.google.firestore.GoogleFirestoreOperations.class;
         case "pollstrategy":
         case "pollStrategy": return 
org.apache.camel.spi.PollingConsumerPollStrategy.class;
@@ -167,6 +171,8 @@ public class GoogleFirestoreEndpointConfigurer extends 
PropertyConfigurerSupport
         case "initialDelay": return target.getInitialDelay();
         case "lazystartproducer":
         case "lazyStartProducer": return target.isLazyStartProducer();
+        case "maxpendingchanges":
+        case "maxPendingChanges": return 
target.getConfiguration().getMaxPendingChanges();
         case "operation": return target.getConfiguration().getOperation();
         case "pollstrategy":
         case "pollStrategy": return target.getPollStrategy();
diff --git 
a/components/camel-google/camel-google-firestore/src/generated/java/org/apache/camel/component/google/firestore/GoogleFirestoreEndpointUriFactory.java
 
b/components/camel-google/camel-google-firestore/src/generated/java/org/apache/camel/component/google/firestore/GoogleFirestoreEndpointUriFactory.java
index e5cdd9aae03d..8be0b19e24b1 100644
--- 
a/components/camel-google/camel-google-firestore/src/generated/java/org/apache/camel/component/google/firestore/GoogleFirestoreEndpointUriFactory.java
+++ 
b/components/camel-google/camel-google-firestore/src/generated/java/org/apache/camel/component/google/firestore/GoogleFirestoreEndpointUriFactory.java
@@ -24,7 +24,7 @@ public class GoogleFirestoreEndpointUriFactory extends 
org.apache.camel.support.
     private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
     private static final Map<String, String> MULTI_VALUE_PREFIXES;
     static {
-        Set<String> props = new HashSet<>(28);
+        Set<String> props = new HashSet<>(29);
         props.add("backoffErrorThreshold");
         props.add("backoffIdleThreshold");
         props.add("backoffMultiplier");
@@ -39,6 +39,7 @@ public class GoogleFirestoreEndpointUriFactory extends 
org.apache.camel.support.
         props.add("greedy");
         props.add("initialDelay");
         props.add("lazyStartProducer");
+        props.add("maxPendingChanges");
         props.add("operation");
         props.add("pollStrategy");
         props.add("projectId");
diff --git 
a/components/camel-google/camel-google-firestore/src/generated/resources/META-INF/org/apache/camel/component/google/firestore/google-firestore.json
 
b/components/camel-google/camel-google-firestore/src/generated/resources/META-INF/org/apache/camel/component/google/firestore/google-firestore.json
index 11f9b15d2195..05587d088f63 100644
--- 
a/components/camel-google/camel-google-firestore/src/generated/resources/META-INF/org/apache/camel/component/google/firestore/google-firestore.json
+++ 
b/components/camel-google/camel-google-firestore/src/generated/resources/META-INF/org/apache/camel/component/google/firestore/google-firestore.json
@@ -31,12 +31,13 @@
     "serviceAccountKey": { "index": 4, "kind": "property", "displayName": 
"Service Account Key", "group": "common", "label": "common", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "The Service account key 
that can be used as credentials for the Firestore cli [...]
     "bridgeErrorHandler": { "index": 5, "kind": "property", "displayName": 
"Bridge Error Handler", "group": "consumer", "label": "consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Allows for bridging the consumer to the Camel routing Error Handler, which 
mean any exceptions (if possible) occurred while the Camel consumer is trying 
to pickup incoming messages, or the like [...]
     "realtimeUpdates": { "index": 6, "kind": "property", "displayName": 
"Realtime Updates", "group": "consumer", "label": "consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, 
"configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "When true, the consumer 
will listen for real-time updates on th [...]
-    "documentId": { "index": 7, "kind": "property", "displayName": "Document 
Id", "group": "producer", "label": "producer", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "The document ID to use 
for document-specific operations" },
-    "lazyStartProducer": { "index": 8, "kind": "property", "displayName": 
"Lazy Start Producer", "group": "producer", "label": "producer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether the producer should be started lazy (on the first message). By 
starting lazy you can use this to allow CamelContext and routes to startup in 
situations where a producer may otherwise fail [...]
-    "operation": { "index": 9, "kind": "property", "displayName": "Operation", 
"group": "producer", "label": "producer", "required": false, "type": "enum", 
"javaType": 
"org.apache.camel.component.google.firestore.GoogleFirestoreOperations", 
"enum": [ "setDocument", "getDocumentById", "updateDocument", "deleteDocument", 
"queryCollection", "listDocuments", "listCollections", "createDocument" ], 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.came [...]
-    "autowiredEnabled": { "index": 10, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching  [...]
-    "healthCheckConsumerEnabled": { "index": 11, "kind": "property", 
"displayName": "Health Check Consumer Enabled", "group": "health", "label": 
"health", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Used for enabling or disabling all consumer based health checks 
from this component" },
-    "healthCheckProducerEnabled": { "index": 12, "kind": "property", 
"displayName": "Health Check Producer Enabled", "group": "health", "label": 
"health", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Used for enabling or disabling all producer based health checks 
from this component. Notice: Camel has by default disabled all producer based 
health-checks. You can turn on produce [...]
+    "maxPendingChanges": { "index": 7, "kind": "property", "displayName": "Max 
Pending Changes", "group": "consumer (advanced)", "label": "consumer,advanced", 
"required": false, "type": "integer", "javaType": "int", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": 0, "configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "Maximum number of 
realtime document changes bu [...]
+    "documentId": { "index": 8, "kind": "property", "displayName": "Document 
Id", "group": "producer", "label": "producer", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "The document ID to use 
for document-specific operations" },
+    "lazyStartProducer": { "index": 9, "kind": "property", "displayName": 
"Lazy Start Producer", "group": "producer", "label": "producer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether the producer should be started lazy (on the first message). By 
starting lazy you can use this to allow CamelContext and routes to startup in 
situations where a producer may otherwise fail [...]
+    "operation": { "index": 10, "kind": "property", "displayName": 
"Operation", "group": "producer", "label": "producer", "required": false, 
"type": "enum", "javaType": 
"org.apache.camel.component.google.firestore.GoogleFirestoreOperations", 
"enum": [ "setDocument", "getDocumentById", "updateDocument", "deleteDocument", 
"queryCollection", "listDocuments", "listCollections", "createDocument" ], 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.cam [...]
+    "autowiredEnabled": { "index": 11, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching  [...]
+    "healthCheckConsumerEnabled": { "index": 12, "kind": "property", 
"displayName": "Health Check Consumer Enabled", "group": "health", "label": 
"health", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Used for enabling or disabling all consumer based health checks 
from this component" },
+    "healthCheckProducerEnabled": { "index": 13, "kind": "property", 
"displayName": "Health Check Producer Enabled", "group": "health", "label": 
"health", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Used for enabling or disabling all producer based health checks 
from this component. Notice: Camel has by default disabled all producer based 
health-checks. You can turn on produce [...]
   },
   "headers": {
     "CamelGoogleFirestoreOperation": { "index": 0, "kind": "header", 
"displayName": "", "group": "producer", "label": "producer", "required": false, 
"javaType": 
"org.apache.camel.component.google.firestore.GoogleFirestoreOperations", 
"enum": [ "setDocument", "getDocumentById", "updateDocument", "deleteDocument", 
"queryCollection", "listDocuments", "listCollections", "createDocument" ], 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The op [...]
@@ -67,23 +68,24 @@
     "bridgeErrorHandler": { "index": 7, "kind": "parameter", "displayName": 
"Bridge Error Handler", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Allows for bridging the consumer to the 
Camel routing Error Handler, which mean any exceptions (if possible) occurred 
while the Camel consumer is trying to pickup incoming  [...]
     "exceptionHandler": { "index": 8, "kind": "parameter", "displayName": 
"Exception Handler", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "object", "javaType": 
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", 
"deprecated": false, "autowired": false, "secret": false, "description": "To 
let the consumer use a custom ExceptionHandler. Notice if the option 
bridgeErrorHandler is enabled then this option is not in use. By def [...]
     "exchangePattern": { "index": 9, "kind": "parameter", "displayName": 
"Exchange Pattern", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "enum", "javaType": 
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], 
"deprecated": false, "autowired": false, "secret": false, "description": "Sets 
the exchange pattern when the consumer creates an exchange." },
-    "pollStrategy": { "index": 10, "kind": "parameter", "displayName": "Poll 
Strategy", "group": "consumer (advanced)", "label": "consumer,advanced", 
"required": false, "type": "object", "javaType": 
"org.apache.camel.spi.PollingConsumerPollStrategy", "deprecated": false, 
"autowired": false, "secret": false, "description": "A pluggable 
org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your 
custom implementation to control error handling usually occurred during the po 
[...]
-    "documentId": { "index": 11, "kind": "parameter", "displayName": "Document 
Id", "group": "producer", "label": "producer", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "The document ID to use 
for document-specific operations" },
-    "operation": { "index": 12, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "producer", "required": false, 
"type": "enum", "javaType": 
"org.apache.camel.component.google.firestore.GoogleFirestoreOperations", 
"enum": [ "setDocument", "getDocumentById", "updateDocument", "deleteDocument", 
"queryCollection", "listDocuments", "listCollections", "createDocument" ], 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.ca [...]
-    "lazyStartProducer": { "index": 13, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produ [...]
-    "backoffErrorThreshold": { "index": 14, "kind": "parameter", 
"displayName": "Backoff Error Threshold", "group": "scheduler", "label": 
"consumer,scheduler", "required": false, "type": "integer", "javaType": "int", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
number of subsequent error polls (failed due some error) that should happen 
before the backoffMultipler should kick-in." },
-    "backoffIdleThreshold": { "index": 15, "kind": "parameter", "displayName": 
"Backoff Idle Threshold", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "integer", "javaType": "int", "deprecated": false, 
"autowired": false, "secret": false, "description": "The number of subsequent 
idle polls that should happen before the backoffMultipler should kick-in." },
-    "backoffMultiplier": { "index": 16, "kind": "parameter", "displayName": 
"Backoff Multiplier", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "integer", "javaType": "int", "deprecated": false, 
"autowired": false, "secret": false, "description": "To let the scheduled 
polling consumer backoff if there has been a number of subsequent idles\/errors 
in a row. The multiplier is then the number of polls that will be skipped 
before the next actual attempt is h [...]
-    "delay": { "index": 17, "kind": "parameter", "displayName": "Delay", 
"group": "scheduler", "label": "consumer,scheduler", "required": false, "type": 
"integer", "javaType": "long", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 500, "description": "Milliseconds before the 
next poll." },
-    "greedy": { "index": 18, "kind": "parameter", "displayName": "Greedy", 
"group": "scheduler", "label": "consumer,scheduler", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": false, "description": "If greedy is enabled, 
then the ScheduledPollConsumer will run immediately again, if the previous run 
polled 1 or more messages." },
-    "initialDelay": { "index": 19, "kind": "parameter", "displayName": 
"Initial Delay", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "integer", "javaType": "long", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": 1000, "description": 
"Milliseconds before the first poll starts." },
-    "repeatCount": { "index": 20, "kind": "parameter", "displayName": "Repeat 
Count", "group": "scheduler", "label": "consumer,scheduler", "required": false, 
"type": "integer", "javaType": "long", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 0, "description": "Specifies a maximum limit 
of number of fires. So if you set it to 1, the scheduler will only fire once. 
If you set it to 5, it will only fire five times. A value of zero or negative 
means fire forever." },
-    "runLoggingLevel": { "index": 21, "kind": "parameter", "displayName": "Run 
Logging Level", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "enum", "javaType": "org.apache.camel.LoggingLevel", 
"enum": [ "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF" ], "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": "TRACE", 
"description": "The consumer logs a start\/complete log line when it polls. 
This option allows you to configure the log [...]
-    "scheduledExecutorService": { "index": 22, "kind": "parameter", 
"displayName": "Scheduled Executor Service", "group": "scheduler", "label": 
"consumer,scheduler", "required": false, "type": "object", "javaType": 
"java.util.concurrent.ScheduledExecutorService", "deprecated": false, 
"autowired": false, "secret": false, "description": "Allows for configuring a 
custom\/shared thread pool to use for the consumer. By default each consumer 
has its own single threaded thread pool." },
-    "scheduler": { "index": 23, "kind": "parameter", "displayName": 
"Scheduler", "group": "scheduler", "label": "consumer,scheduler", "required": 
false, "type": "object", "javaType": "java.lang.Object", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": "none", "description": "To 
use a cron scheduler from either camel-spring or camel-quartz component. Use 
value spring or quartz for built in scheduler" },
-    "schedulerProperties": { "index": 24, "kind": "parameter", "displayName": 
"Scheduler Properties", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "object", "javaType": 
"java.util.Map<java.lang.String, java.lang.Object>", "prefix": "scheduler.", 
"multiValue": true, "deprecated": false, "autowired": false, "secret": false, 
"description": "To configure additional properties when using a custom 
scheduler or any of the Quartz, Spring based scheduler. This i [...]
-    "startScheduler": { "index": 25, "kind": "parameter", "displayName": 
"Start Scheduler", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "boolean", "javaType": "boolean", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Whether the scheduler should be auto started." },
-    "timeUnit": { "index": 26, "kind": "parameter", "displayName": "Time 
Unit", "group": "scheduler", "label": "consumer,scheduler", "required": false, 
"type": "enum", "javaType": "java.util.concurrent.TimeUnit", "enum": [ 
"NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS", "MINUTES", "HOURS", 
"DAYS" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "MILLISECONDS", "description": "Time unit for initialDelay and 
delay options." },
-    "useFixedDelay": { "index": 27, "kind": "parameter", "displayName": "Use 
Fixed Delay", "group": "scheduler", "label": "consumer,scheduler", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in 
JDK for details." }
+    "maxPendingChanges": { "index": 10, "kind": "parameter", "displayName": 
"Max Pending Changes", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "integer", "javaType": "int", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, 
"configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "Maximum number of 
realtime document changes  [...]
+    "pollStrategy": { "index": 11, "kind": "parameter", "displayName": "Poll 
Strategy", "group": "consumer (advanced)", "label": "consumer,advanced", 
"required": false, "type": "object", "javaType": 
"org.apache.camel.spi.PollingConsumerPollStrategy", "deprecated": false, 
"autowired": false, "secret": false, "description": "A pluggable 
org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your 
custom implementation to control error handling usually occurred during the po 
[...]
+    "documentId": { "index": 12, "kind": "parameter", "displayName": "Document 
Id", "group": "producer", "label": "producer", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.google.firestore.GoogleFirestoreConfiguration", 
"configurationField": "configuration", "description": "The document ID to use 
for document-specific operations" },
+    "operation": { "index": 13, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "producer", "required": false, 
"type": "enum", "javaType": 
"org.apache.camel.component.google.firestore.GoogleFirestoreOperations", 
"enum": [ "setDocument", "getDocumentById", "updateDocument", "deleteDocument", 
"queryCollection", "listDocuments", "listCollections", "createDocument" ], 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.ca [...]
+    "lazyStartProducer": { "index": 14, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produ [...]
+    "backoffErrorThreshold": { "index": 15, "kind": "parameter", 
"displayName": "Backoff Error Threshold", "group": "scheduler", "label": 
"consumer,scheduler", "required": false, "type": "integer", "javaType": "int", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
number of subsequent error polls (failed due some error) that should happen 
before the backoffMultipler should kick-in." },
+    "backoffIdleThreshold": { "index": 16, "kind": "parameter", "displayName": 
"Backoff Idle Threshold", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "integer", "javaType": "int", "deprecated": false, 
"autowired": false, "secret": false, "description": "The number of subsequent 
idle polls that should happen before the backoffMultipler should kick-in." },
+    "backoffMultiplier": { "index": 17, "kind": "parameter", "displayName": 
"Backoff Multiplier", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "integer", "javaType": "int", "deprecated": false, 
"autowired": false, "secret": false, "description": "To let the scheduled 
polling consumer backoff if there has been a number of subsequent idles\/errors 
in a row. The multiplier is then the number of polls that will be skipped 
before the next actual attempt is h [...]
+    "delay": { "index": 18, "kind": "parameter", "displayName": "Delay", 
"group": "scheduler", "label": "consumer,scheduler", "required": false, "type": 
"integer", "javaType": "long", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 500, "description": "Milliseconds before the 
next poll." },
+    "greedy": { "index": 19, "kind": "parameter", "displayName": "Greedy", 
"group": "scheduler", "label": "consumer,scheduler", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": false, "description": "If greedy is enabled, 
then the ScheduledPollConsumer will run immediately again, if the previous run 
polled 1 or more messages." },
+    "initialDelay": { "index": 20, "kind": "parameter", "displayName": 
"Initial Delay", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "integer", "javaType": "long", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": 1000, "description": 
"Milliseconds before the first poll starts." },
+    "repeatCount": { "index": 21, "kind": "parameter", "displayName": "Repeat 
Count", "group": "scheduler", "label": "consumer,scheduler", "required": false, 
"type": "integer", "javaType": "long", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 0, "description": "Specifies a maximum limit 
of number of fires. So if you set it to 1, the scheduler will only fire once. 
If you set it to 5, it will only fire five times. A value of zero or negative 
means fire forever." },
+    "runLoggingLevel": { "index": 22, "kind": "parameter", "displayName": "Run 
Logging Level", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "enum", "javaType": "org.apache.camel.LoggingLevel", 
"enum": [ "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF" ], "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": "TRACE", 
"description": "The consumer logs a start\/complete log line when it polls. 
This option allows you to configure the log [...]
+    "scheduledExecutorService": { "index": 23, "kind": "parameter", 
"displayName": "Scheduled Executor Service", "group": "scheduler", "label": 
"consumer,scheduler", "required": false, "type": "object", "javaType": 
"java.util.concurrent.ScheduledExecutorService", "deprecated": false, 
"autowired": false, "secret": false, "description": "Allows for configuring a 
custom\/shared thread pool to use for the consumer. By default each consumer 
has its own single threaded thread pool." },
+    "scheduler": { "index": 24, "kind": "parameter", "displayName": 
"Scheduler", "group": "scheduler", "label": "consumer,scheduler", "required": 
false, "type": "object", "javaType": "java.lang.Object", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": "none", "description": "To 
use a cron scheduler from either camel-spring or camel-quartz component. Use 
value spring or quartz for built in scheduler" },
+    "schedulerProperties": { "index": 25, "kind": "parameter", "displayName": 
"Scheduler Properties", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "object", "javaType": 
"java.util.Map<java.lang.String, java.lang.Object>", "prefix": "scheduler.", 
"multiValue": true, "deprecated": false, "autowired": false, "secret": false, 
"description": "To configure additional properties when using a custom 
scheduler or any of the Quartz, Spring based scheduler. This i [...]
+    "startScheduler": { "index": 26, "kind": "parameter", "displayName": 
"Start Scheduler", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "boolean", "javaType": "boolean", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Whether the scheduler should be auto started." },
+    "timeUnit": { "index": 27, "kind": "parameter", "displayName": "Time 
Unit", "group": "scheduler", "label": "consumer,scheduler", "required": false, 
"type": "enum", "javaType": "java.util.concurrent.TimeUnit", "enum": [ 
"NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS", "MINUTES", "HOURS", 
"DAYS" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "MILLISECONDS", "description": "Time unit for initialDelay and 
delay options." },
+    "useFixedDelay": { "index": 28, "kind": "parameter", "displayName": "Use 
Fixed Delay", "group": "scheduler", "label": "consumer,scheduler", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in 
JDK for details." }
   }
 }
diff --git 
a/components/camel-google/camel-google-firestore/src/main/docs/google-firestore-component.adoc
 
b/components/camel-google/camel-google-firestore/src/main/docs/google-firestore-component.adoc
index 3fc57d7b5684..62929b4ed958 100644
--- 
a/components/camel-google/camel-google-firestore/src/main/docs/google-firestore-component.adoc
+++ 
b/components/camel-google/camel-google-firestore/src/main/docs/google-firestore-component.adoc
@@ -243,6 +243,18 @@ YAML::
 ----
 ====
 
+In real-time mode the document changes reported by the listener are buffered 
until the next poll picks them up. That
+buffer is unbounded by default, so a collection changing faster than the route 
consumes it makes the buffer grow, and
+it keeps growing while the route is suspended. Set `maxPendingChanges` to 
bound it: when the buffer is full the oldest
+buffered change is discarded and a warning is logged, which leaves the route 
with the most recent state of the
+collection.
+
+[source,java]
+----
+from("google-firestore://myCollection?realtimeUpdates=true&maxPendingChanges=1000")
+    .to("log:changes");
+----
+
 === Advanced Component Configuration
 
 If you need more control over the `Firestore` client instance configuration, 
you can create your own instance and refer to it in your Camel google-firestore 
component configuration:
diff --git 
a/components/camel-google/camel-google-firestore/src/main/java/org/apache/camel/component/google/firestore/GoogleFirestoreConfiguration.java
 
b/components/camel-google/camel-google-firestore/src/main/java/org/apache/camel/component/google/firestore/GoogleFirestoreConfiguration.java
index 0b4fcbb99ab2..655cbacd294b 100644
--- 
a/components/camel-google/camel-google-firestore/src/main/java/org/apache/camel/component/google/firestore/GoogleFirestoreConfiguration.java
+++ 
b/components/camel-google/camel-google-firestore/src/main/java/org/apache/camel/component/google/firestore/GoogleFirestoreConfiguration.java
@@ -62,6 +62,14 @@ public class GoogleFirestoreConfiguration implements 
Cloneable, GoogleCommonConf
               description = "When true, the consumer will listen for real-time 
updates on the collection")
     private boolean realtimeUpdates;
 
+    @UriParam(label = "consumer,advanced", defaultValue = "0",
+              description = "Maximum number of realtime document changes 
buffered between two polls. The changes reported by"
+                            + " the snapshot listener are buffered until the 
next poll picks them up, so a collection"
+                            + " changing faster than the route consumes it 
makes that buffer grow. When the buffer is full"
+                            + " the oldest buffered change is discarded and a 
warning is logged. Use 0 for an unbounded"
+                            + " buffer. Only used when realtimeUpdates is 
enabled.")
+    private int maxPendingChanges;
+
     public String getCollectionName() {
         return collectionName;
     }
@@ -153,6 +161,20 @@ public class GoogleFirestoreConfiguration implements 
Cloneable, GoogleCommonConf
         this.realtimeUpdates = realtimeUpdates;
     }
 
+    public int getMaxPendingChanges() {
+        return maxPendingChanges;
+    }
+
+    /**
+     * Maximum number of realtime document changes buffered between two polls. 
The changes reported by the snapshot
+     * listener are buffered until the next poll picks them up, so a 
collection changing faster than the route consumes
+     * it makes that buffer grow. When the buffer is full the oldest buffered 
change is discarded and a warning is
+     * logged. Use 0 for an unbounded buffer. Only used when realtimeUpdates 
is enabled.
+     */
+    public void setMaxPendingChanges(int maxPendingChanges) {
+        this.maxPendingChanges = maxPendingChanges;
+    }
+
     public GoogleFirestoreConfiguration copy() {
         try {
             return (GoogleFirestoreConfiguration) super.clone();
diff --git 
a/components/camel-google/camel-google-firestore/src/main/java/org/apache/camel/component/google/firestore/GoogleFirestoreConsumer.java
 
b/components/camel-google/camel-google-firestore/src/main/java/org/apache/camel/component/google/firestore/GoogleFirestoreConsumer.java
index d39188edb737..33913bb08edb 100644
--- 
a/components/camel-google/camel-google-firestore/src/main/java/org/apache/camel/component/google/firestore/GoogleFirestoreConsumer.java
+++ 
b/components/camel-google/camel-google-firestore/src/main/java/org/apache/camel/component/google/firestore/GoogleFirestoreConsumer.java
@@ -16,12 +16,16 @@
  */
 package org.apache.camel.component.google.firestore;
 
+import java.util.ArrayList;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
 
 import com.google.cloud.firestore.CollectionReference;
 import com.google.cloud.firestore.DocumentChange;
@@ -46,14 +50,19 @@ import org.slf4j.LoggerFactory;
 public class GoogleFirestoreConsumer extends ScheduledBatchPollingConsumer {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(GoogleFirestoreConsumer.class);
+    private static final long DISCARD_LOG_INTERVAL = 100;
 
     private final GoogleFirestoreEndpoint endpoint;
     private ListenerRegistration listenerRegistration;
-    private final Queue<Exchange> pendingExchanges = new 
ConcurrentLinkedQueue<>();
+    private final BlockingQueue<Exchange> pendingExchanges;
+    private final AtomicLong discardedChanges = new AtomicLong();
 
     public GoogleFirestoreConsumer(GoogleFirestoreEndpoint endpoint, Processor 
processor) {
         super(endpoint, processor);
         this.endpoint = endpoint;
+
+        int max = endpoint.getConfiguration().getMaxPendingChanges();
+        this.pendingExchanges = max > 0 ? new LinkedBlockingQueue<>(max) : new 
LinkedBlockingQueue<>();
     }
 
     @Override
@@ -72,6 +81,14 @@ public class GoogleFirestoreConsumer extends 
ScheduledBatchPollingConsumer {
             listenerRegistration = null;
             LOG.debug("Realtime listener removed");
         }
+
+        List<Exchange> remaining = new ArrayList<>();
+        pendingExchanges.drainTo(remaining);
+        if (!remaining.isEmpty()) {
+            LOG.debug("Releasing {} buffered document changes that were not 
polled", remaining.size());
+            remaining.forEach(exchange -> releaseExchange(exchange, false));
+        }
+
         super.doStop();
     }
 
@@ -93,7 +110,7 @@ public class GoogleFirestoreConsumer extends 
ScheduledBatchPollingConsumer {
                     for (DocumentChange dc : snapshots.getDocumentChanges()) {
                         try {
                             Exchange exchange = 
createExchangeFromDocument(dc.getDocument(), dc.getType());
-                            pendingExchanges.add(exchange);
+                            bufferChange(exchange);
                         } catch (Exception ex) {
                             LOG.error("Error creating exchange from document 
change", ex);
                         }
@@ -112,17 +129,46 @@ public class GoogleFirestoreConsumer extends 
ScheduledBatchPollingConsumer {
         }
     }
 
+    /**
+     * The document changes buffered by the realtime listener and not yet 
picked up by a poll.
+     */
+    BlockingQueue<Exchange> pendingChanges() {
+        return pendingExchanges;
+    }
+
+    /**
+     * Buffers a document change until the next poll picks it up. The listener 
callback runs on a Firestore client
+     * thread, so it must never block waiting for the route to catch up: when 
the buffer is bounded and full, the oldest
+     * buffered change is discarded instead, leaving the route with the most 
recent state of the collection.
+     */
+    void bufferChange(Exchange exchange) {
+        while (!pendingExchanges.offer(exchange)) {
+            Exchange discarded = pendingExchanges.poll();
+            if (discarded == null) {
+                // the poll drained the buffer in the meantime, so there is 
room again
+                continue;
+            }
+
+            releaseExchange(discarded, false);
+            long total = discardedChanges.incrementAndGet();
+            if (total == 1 || total % DISCARD_LOG_INTERVAL == 0) {
+                LOG.warn("The realtime buffer of collection {} is full 
(maxPendingChanges={}), discarding the oldest"
+                         + " buffered change. {} changes discarded so far. 
Raise maxPendingChanges, or make the route"
+                         + " consume faster.",
+                        endpoint.getConfiguration().getCollectionName(),
+                        endpoint.getConfiguration().getMaxPendingChanges(), 
total);
+            }
+        }
+    }
+
     @Override
     protected int poll() throws Exception {
         Queue<Exchange> exchanges;
 
         if (endpoint.getConfiguration().isRealtimeUpdates()) {
-            // Drain pending exchanges from realtime listener (lock-free)
+            // Drain pending exchanges from realtime listener
             exchanges = new LinkedList<>();
-            Exchange e;
-            while ((e = pendingExchanges.poll()) != null) {
-                exchanges.add(e);
-            }
+            pendingExchanges.drainTo(exchanges);
         } else {
             // Poll the collection
             exchanges = pollCollection();
@@ -189,6 +235,12 @@ public class GoogleFirestoreConsumer extends 
ScheduledBatchPollingConsumer {
             getAsyncProcessor().process(exchange, EmptyAsyncCallback.get());
         }
 
+        // the batch is interrupted when the consumer is stopping, so release 
what is left over
+        Exchange remaining;
+        while ((remaining = (Exchange) exchanges.poll()) != null) {
+            releaseExchange(remaining, false);
+        }
+
         return total;
     }
 
diff --git 
a/components/camel-google/camel-google-firestore/src/test/java/org/apache/camel/component/google/firestore/GoogleFirestoreConsumerBufferTest.java
 
b/components/camel-google/camel-google-firestore/src/test/java/org/apache/camel/component/google/firestore/GoogleFirestoreConsumerBufferTest.java
new file mode 100644
index 000000000000..6c3c25677f1b
--- /dev/null
+++ 
b/components/camel-google/camel-google-firestore/src/test/java/org/apache/camel/component/google/firestore/GoogleFirestoreConsumerBufferTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.google.firestore;
+
+import java.util.List;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Verifies how the realtime listener buffers the document changes it reports. 
Uses direct object construction to avoid
+ * starting the endpoint, which requires Google Cloud credentials.
+ */
+class GoogleFirestoreConsumerBufferTest {
+
+    private DefaultCamelContext context;
+
+    @AfterEach
+    void tearDown() {
+        if (context != null) {
+            context.stop();
+        }
+    }
+
+    private GoogleFirestoreConsumer consumer(int maxPendingChanges) {
+        context = new DefaultCamelContext();
+
+        GoogleFirestoreConfiguration configuration = new 
GoogleFirestoreConfiguration();
+        configuration.setCollectionName("users");
+        configuration.setRealtimeUpdates(true);
+        configuration.setMaxPendingChanges(maxPendingChanges);
+
+        GoogleFirestoreComponent component = new 
GoogleFirestoreComponent(context);
+        GoogleFirestoreEndpoint endpoint = new 
GoogleFirestoreEndpoint("google-firestore:users", component, configuration);
+
+        return new GoogleFirestoreConsumer(endpoint, exchange -> {
+        });
+    }
+
+    private Exchange change(GoogleFirestoreConsumer consumer, String 
documentId) {
+        Exchange exchange = new DefaultExchange(context);
+        
exchange.getIn().setHeader(GoogleFirestoreConstants.RESPONSE_DOCUMENT_ID, 
documentId);
+        consumer.bufferChange(exchange);
+        return exchange;
+    }
+
+    private List<String> bufferedDocumentIds(GoogleFirestoreConsumer consumer) 
{
+        return consumer.pendingChanges().stream()
+                .map(exchange -> 
exchange.getIn().getHeader(GoogleFirestoreConstants.RESPONSE_DOCUMENT_ID, 
String.class))
+                .toList();
+    }
+
+    @Test
+    void theBufferIsUnboundedByDefault() {
+        GoogleFirestoreConsumer consumer = consumer(0);
+
+        for (int i = 0; i < 500; i++) {
+            change(consumer, "doc-" + i);
+        }
+
+        assertThat(consumer.pendingChanges()).hasSize(500);
+    }
+
+    @Test
+    void theOldestChangeIsDiscardedWhenTheBufferIsFull() {
+        GoogleFirestoreConsumer consumer = consumer(3);
+
+        change(consumer, "doc-1");
+        change(consumer, "doc-2");
+        change(consumer, "doc-3");
+        change(consumer, "doc-4");
+        change(consumer, "doc-5");
+
+        // the route is left with the most recent state of the collection
+        assertThat(bufferedDocumentIds(consumer)).containsExactly("doc-3", 
"doc-4", "doc-5");
+    }
+
+    @Test
+    void aBufferOfOneKeepsOnlyTheLastChange() {
+        GoogleFirestoreConsumer consumer = consumer(1);
+
+        change(consumer, "doc-1");
+        change(consumer, "doc-2");
+
+        assertThat(bufferedDocumentIds(consumer)).containsExactly("doc-2");
+    }
+}
diff --git 
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/GoogleFirestoreComponentBuilderFactory.java
 
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/GoogleFirestoreComponentBuilderFactory.java
index f0e4a65ddab8..375d966f8f69 100644
--- 
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/GoogleFirestoreComponentBuilderFactory.java
+++ 
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/GoogleFirestoreComponentBuilderFactory.java
@@ -178,6 +178,27 @@ public interface GoogleFirestoreComponentBuilderFactory {
             return this;
         }
     
+        /**
+         * Maximum number of realtime document changes buffered between two
+         * polls. The changes reported by the snapshot listener are buffered
+         * until the next poll picks them up, so a collection changing faster
+         * than the route consumes it makes that buffer grow. When the buffer 
is
+         * full the oldest buffered change is discarded and a warning is 
logged.
+         * Use 0 for an unbounded buffer. Only used when realtimeUpdates is
+         * enabled.
+         * 
+         * The option is a: &lt;code&gt;int&lt;/code&gt; type.
+         * 
+         * Group: consumer (advanced)
+         * 
+         * @param maxPendingChanges the value to set
+         * @return the dsl builder
+         */
+        default GoogleFirestoreComponentBuilder maxPendingChanges(int 
maxPendingChanges) {
+            doSetProperty("maxPendingChanges", maxPendingChanges);
+            return this;
+        }
+    
         /**
          * The document ID to use for document-specific operations.
          * 
@@ -321,6 +342,7 @@ public interface GoogleFirestoreComponentBuilderFactory {
             case "serviceAccountKey": 
getOrCreateConfiguration((GoogleFirestoreComponent) 
component).setServiceAccountKey((java.lang.String) value); return true;
             case "bridgeErrorHandler": ((GoogleFirestoreComponent) 
component).setBridgeErrorHandler((boolean) value); return true;
             case "realtimeUpdates": 
getOrCreateConfiguration((GoogleFirestoreComponent) 
component).setRealtimeUpdates((boolean) value); return true;
+            case "maxPendingChanges": 
getOrCreateConfiguration((GoogleFirestoreComponent) 
component).setMaxPendingChanges((int) value); return true;
             case "documentId": 
getOrCreateConfiguration((GoogleFirestoreComponent) 
component).setDocumentId((java.lang.String) value); return true;
             case "lazyStartProducer": ((GoogleFirestoreComponent) 
component).setLazyStartProducer((boolean) value); return true;
             case "operation": 
getOrCreateConfiguration((GoogleFirestoreComponent) 
component).setOperation((org.apache.camel.component.google.firestore.GoogleFirestoreOperations)
 value); return true;
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/GoogleFirestoreEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/GoogleFirestoreEndpointBuilderFactory.java
index 72e3b7a18e6b..a77763ded14e 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/GoogleFirestoreEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/GoogleFirestoreEndpointBuilderFactory.java
@@ -769,6 +769,48 @@ public interface GoogleFirestoreEndpointBuilderFactory {
             doSetProperty("exchangePattern", exchangePattern);
             return this;
         }
+        /**
+         * Maximum number of realtime document changes buffered between two
+         * polls. The changes reported by the snapshot listener are buffered
+         * until the next poll picks them up, so a collection changing faster
+         * than the route consumes it makes that buffer grow. When the buffer 
is
+         * full the oldest buffered change is discarded and a warning is 
logged.
+         * Use 0 for an unbounded buffer. Only used when realtimeUpdates is
+         * enabled.
+         * 
+         * The option is a: <code>int</code> type.
+         * 
+         * Default: 0
+         * Group: consumer (advanced)
+         * 
+         * @param maxPendingChanges the value to set
+         * @return the dsl builder
+         */
+        default AdvancedGoogleFirestoreEndpointConsumerBuilder 
maxPendingChanges(int maxPendingChanges) {
+            doSetProperty("maxPendingChanges", maxPendingChanges);
+            return this;
+        }
+        /**
+         * Maximum number of realtime document changes buffered between two
+         * polls. The changes reported by the snapshot listener are buffered
+         * until the next poll picks them up, so a collection changing faster
+         * than the route consumes it makes that buffer grow. When the buffer 
is
+         * full the oldest buffered change is discarded and a warning is 
logged.
+         * Use 0 for an unbounded buffer. Only used when realtimeUpdates is
+         * enabled.
+         * 
+         * The option will be converted to a <code>int</code> type.
+         * 
+         * Default: 0
+         * Group: consumer (advanced)
+         * 
+         * @param maxPendingChanges the value to set
+         * @return the dsl builder
+         */
+        default AdvancedGoogleFirestoreEndpointConsumerBuilder 
maxPendingChanges(String maxPendingChanges) {
+            doSetProperty("maxPendingChanges", maxPendingChanges);
+            return this;
+        }
         /**
          * A pluggable org.apache.camel.PollingConsumerPollingStrategy allowing
          * you to provide your custom implementation to control error handling

Reply via email to