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 b2fc5a39bb5b CAMEL-23806: Split azure-storage-blob docs into focused
sub-pages
b2fc5a39bb5b is described below
commit b2fc5a39bb5b8086e10a50e048a4b1d06f1a79d4
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Jun 20 18:53:19 2026 +0200
CAMEL-23806: Split azure-storage-blob docs into focused sub-pages
Split the Azure Storage Blob component documentation (1,956 lines) into
2 focused sub-pages, reducing the main page to 264 lines. Repetitive
producer operation examples condensed into a common pattern section with
a reference table, keeping detailed examples only for unique operations.
- azure-storage-blob-operations: 30 producer operations,
snapshots/versions, SAS tokens
- azure-storage-blob-consumer: consumer patterns (delete/move after read)
Closes #24152
Co-Authored-By: Claude <[email protected]>
---
.../main/docs/azure-storage-blob-component.adoc | 1699 +-------------------
.../src/main/docs/azure-storage-blob-consumer.adoc | 282 ++++
.../main/docs/azure-storage-blob-operations.adoc | 641 ++++++++
docs/components/modules/others/nav.adoc | 2 +
.../others/pages/azure-storage-blob-consumer.adoc | 1 +
.../pages/azure-storage-blob-operations.adoc | 1 +
6 files changed, 931 insertions(+), 1695 deletions(-)
diff --git
a/components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-component.adoc
b/components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-component.adoc
index 6c02d53d2284..250efd0ace8a 100644
---
a/components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-component.adoc
+++
b/components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-component.adoc
@@ -239,1703 +239,12 @@ and existing blocks together. Any blocks not specified
in the block list and per
Refer to the example section in this page to learn how to use these operations
into your camel application.
-== Examples
+== Sub-Pages
-=== Consumer Examples
+For more details on specific features, see:
-To consume a blob into a file using the file component, this can be done like
this:
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("azure-storage-blob://camelazure/container1?blobName=hello.txt&accountName=yourAccountName&accessKey=yourAccessKey")
- .to("file://blobdirectory");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from
uri="azure-storage-blob://camelazure/container1?blobName=hello.txt&accountName=yourAccountName&accessKey=yourAccessKey"/>
- <to uri="file://blobdirectory"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: hello.txt
- accountName: yourAccountName
- accessKey: yourAccessKey
- steps:
- - to:
- uri: file://blobdirectory
-----
-====
-
-However, you can also write to file directly without using the file component,
you will need to specify `fileDir` folder path to save your blob in your
machine.
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("azure-storage-blob://camelazure/container1?blobName=hello.txt&accountName=yourAccountName&accessKey=yourAccessKey&fileDir=/var/to/awesome/dir")
- .to("mock:results");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from
uri="azure-storage-blob://camelazure/container1?blobName=hello.txt&accountName=yourAccountName&accessKey=yourAccessKey&fileDir=/var/to/awesome/dir"/>
- <to uri="mock:results"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: hello.txt
- accountName: yourAccountName
- accessKey: yourAccessKey
- fileDir: /var/to/awesome/dir
- steps:
- - to:
- uri: mock:results
-----
-====
-
-Also, the component supports batch consumer, hence you can consume multiple
blobs with only specifying the container name, the consumer will
-return multiple exchanges depending on the number of the blobs in the
container. Example:
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("azure-storage-blob://camelazure/container1?accountName=yourAccountName&accessKey=yourAccessKey&fileDir=/var/to/awesome/dir")
- .to("mock:results");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from
uri="azure-storage-blob://camelazure/container1?accountName=yourAccountName&accessKey=yourAccessKey&fileDir=/var/to/awesome/dir"/>
- <to uri="mock:results"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- accountName: yourAccountName
- accessKey: yourAccessKey
- fileDir: /var/to/awesome/dir
- steps:
- - to:
- uri: mock:results
-----
-====
-
-==== Delete After Read
-
-The consumer supports automatic deletion of blobs after they have been
successfully processed. This is useful when you want to ensure that blobs are
only processed once.
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("azure-storage-blob://camelazure/container1?deleteAfterRead=true&accessKey=RAW(yourAccessKey)")
- .log("Processing blob: ${header.CamelAzureStorageBlobBlobName}")
- .to("direct:processBlob");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from
uri="azure-storage-blob://camelazure/container1?deleteAfterRead=true&accessKey=RAW(yourAccessKey)"/>
- <log message="Processing blob: ${header.CamelAzureStorageBlobBlobName}"/>
- <to uri="direct:processBlob"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- deleteAfterRead: true
- accessKey: "RAW(yourAccessKey)"
- steps:
- - log:
- message: "Processing blob: ${header.CamelAzureStorageBlobBlobName}"
- - to:
- uri: direct:processBlob
-----
-====
-
-IMPORTANT: The delete operation is only performed if the Exchange is
successfully committed. If processing fails or a rollback occurs, the blob will
not be deleted and can be reprocessed on the next poll.
-
-NOTE: When `deleteAfterRead` is set to `false` (the default), the same blobs
will be retrieved repeatedly in subsequent polls. In this case, you should use
the Idempotent Consumer EIP to filter out duplicates based on the
`CamelAzureStorageBlobBlobName` header.
-
-==== Move After Read
-
-The consumer can move blobs to a different container after successful
processing. This is useful for archiving processed blobs or implementing a
processing pipeline across containers.
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("azure-storage-blob://camelazure/incoming?moveAfterRead=true&destinationContainer=archive&accessKey=RAW(yourAccessKey)")
- .log("Processing blob: ${header.CamelAzureStorageBlobBlobName}")
- .to("direct:processBlob");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from
uri="azure-storage-blob://camelazure/incoming?moveAfterRead=true&destinationContainer=archive&accessKey=RAW(yourAccessKey)"/>
- <log message="Processing blob: ${header.CamelAzureStorageBlobBlobName}"/>
- <to uri="direct:processBlob"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: azure-storage-blob://camelazure/incoming
- parameters:
- moveAfterRead: true
- destinationContainer: archive
- accessKey: "RAW(yourAccessKey)"
- steps:
- - log:
- message: "Processing blob: ${header.CamelAzureStorageBlobBlobName}"
- - to:
- uri: direct:processBlob
-----
-====
-
-You can also customize the destination blob name by adding a prefix and/or
suffix:
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("azure-storage-blob://camelazure/source?moveAfterRead=true&destinationContainer=archive&prefix=incoming/&removePrefixOnMove=true&destinationBlobPrefix=processed/&destinationBlobSuffix=.done&accessKey=RAW(yourAccessKey)")
- .log("Processing: ${header.CamelAzureStorageBlobBlobName}")
- .to("direct:processBlob");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from
uri="azure-storage-blob://camelazure/source?moveAfterRead=true&destinationContainer=archive&prefix=incoming/&removePrefixOnMove=true&destinationBlobPrefix=processed/&destinationBlobSuffix=.done&accessKey=RAW(yourAccessKey)"/>
- <log message="Processing: ${header.CamelAzureStorageBlobBlobName}"/>
- <to uri="direct:processBlob"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: azure-storage-blob://camelazure/source
- parameters:
- moveAfterRead: true
- destinationContainer: archive
- prefix: "incoming/"
- removePrefixOnMove: true
- destinationBlobPrefix: "processed/"
- destinationBlobSuffix: ".done"
- accessKey: "RAW(yourAccessKey)"
- steps:
- - log:
- message: "Processing: ${header.CamelAzureStorageBlobBlobName}"
- - to:
- uri: direct:processBlob
-----
-====
-
-The move operation works as follows:
-
-1. The blob is copied to the destination container with the new name
-2. The original blob is deleted from the source container
-3. Both operations only occur after the Exchange is successfully committed
-
-The following options control the move behavior:
-
-[width="100%",cols="25%,75%",options="header",]
-|===
-|Option |Description
-|`moveAfterRead` |Enable moving blobs after successful processing
-|`destinationContainer` |Target container for moved blobs (required when
`moveAfterRead=true`)
-|`destinationBlobPrefix` |Prefix to add to the blob name in the destination
-|`destinationBlobSuffix` |Suffix to add to the blob name in the destination
-|`removePrefixOnMove` |Remove the source prefix from the blob name before
adding destination prefix
-|===
-
-=== Producer Operations Examples
-- `listBlobContainers`:
-
-NOTE: The `CamelAzureStorageBlobListBlobContainersOptions` header requires a
`ListBlobContainersOptions` object, which must be set from a bean or processor.
-
-._Java-only: programmatic ListBlobContainersOptions_
-
-[source,java]
-----
-from("direct:start")
- .process(exchange -> {
-
exchange.getIn().setHeader("CamelAzureStorageBlobListBlobContainersOptions",
- new ListBlobContainersOptions().setMaxResultsPerPage(10));
- })
-
.to("azure-storage-blob://camelazure?operation=listBlobContainers&serviceClient=#client")
- .to("mock:result");
-----
-
-- `createBlobContainer`:
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:start")
- .setHeader("CamelAzureStorageBlobBlobContainerName",
constant("newContainerName"))
-
.to("azure-storage-blob://camelazure/container1?operation=createBlobContainer&serviceClient=#client")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:start"/>
- <setHeader name="CamelAzureStorageBlobBlobContainerName">
- <constant>newContainerName</constant>
- </setHeader>
- <to
uri="azure-storage-blob://camelazure/container1?operation=createBlobContainer&serviceClient=#client"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:start
- steps:
- - setHeader:
- name: CamelAzureStorageBlobBlobContainerName
- constant: newContainerName
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- operation: createBlobContainer
- serviceClient: "#client"
- - to:
- uri: mock:result
-----
-====
-
-- `deleteBlobContainer`:
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:start")
- .setHeader("CamelAzureStorageBlobBlobContainerName",
constant("overridenName"))
-
.to("azure-storage-blob://camelazure/container1?operation=deleteBlobContainer&serviceClient=#client")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:start"/>
- <setHeader name="CamelAzureStorageBlobBlobContainerName">
- <constant>overridenName</constant>
- </setHeader>
- <to
uri="azure-storage-blob://camelazure/container1?operation=deleteBlobContainer&serviceClient=#client"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:start
- steps:
- - setHeader:
- name: CamelAzureStorageBlobBlobContainerName
- constant: overridenName
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- operation: deleteBlobContainer
- serviceClient: "#client"
- - to:
- uri: mock:result
-----
-====
-
-- `listBlobs`:
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:start")
- .setHeader("CamelAzureStorageBlobBlobContainerName",
constant("overridenName"))
-
.to("azure-storage-blob://camelazure/container1?operation=listBlobs&serviceClient=#client")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:start"/>
- <setHeader name="CamelAzureStorageBlobBlobContainerName">
- <constant>overridenName</constant>
- </setHeader>
- <to
uri="azure-storage-blob://camelazure/container1?operation=listBlobs&serviceClient=#client"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:start
- steps:
- - setHeader:
- name: CamelAzureStorageBlobBlobContainerName
- constant: overridenName
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- operation: listBlobs
- serviceClient: "#client"
- - to:
- uri: mock:result
-----
-====
-
-
-- `listBlobVersions`:
-
-Returns every version of every blob in the container. Versioning must be
enabled on the storage
-account. Each `BlobItem` in the result carries its own `versionId` and
`isCurrentVersion` flag.
-The `prefix` and `regex` options can be used to narrow the result down to a
single blob name.
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:start")
- .setHeader("CamelAzureStorageBlobPrefix", constant("invoice.pdf"))
-
.to("azure-storage-blob://camelazure/container1?operation=listBlobVersions&serviceClient=#client")
- .log("${body}")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:start"/>
- <setHeader name="CamelAzureStorageBlobPrefix">
- <constant>invoice.pdf</constant>
- </setHeader>
- <to
uri="azure-storage-blob://camelazure/container1?operation=listBlobVersions&serviceClient=#client"/>
- <log message="${body}"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:start
- steps:
- - setHeader:
- name: CamelAzureStorageBlobPrefix
- constant: invoice.pdf
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- operation: listBlobVersions
- serviceClient: "#client"
- - log:
- message: "${body}"
- - to:
- uri: mock:result
-----
-====
-
-
-- `getBlob`:
-
-We can either set an `outputStream` in the exchange body and write the data to
it:
-
-._Java-only: programmatic OutputStream handling_
-
-[source,java]
-----
-from("direct:start")
- .process(exchange -> {
- exchange.getIn().setHeader("CamelAzureStorageBlobBlobContainerName",
"overridenName");
- exchange.getIn().setBody(outputStream);
- })
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=getBlob&serviceClient=#client")
- .to("mock:result");
-----
-
-If we don't set a body, then this operation will give us an `InputStream`
instance which can be processed further downstream:
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:start")
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=getBlob&serviceClient=#client")
- .log("${body}")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:start"/>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=blob&operation=getBlob&serviceClient=#client"/>
- <log message="${body}"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:start
- steps:
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: blob
- operation: getBlob
- serviceClient: "#client"
- - log:
- message: "${body}"
- - to:
- uri: mock:result
-----
-====
-
-- `deleteBlob`:
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:start")
- .setHeader("CamelAzureStorageBlobBlobName", constant("overridenName"))
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=deleteBlob&serviceClient=#client")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:start"/>
- <setHeader name="CamelAzureStorageBlobBlobName">
- <constant>overridenName</constant>
- </setHeader>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=blob&operation=deleteBlob&serviceClient=#client"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:start
- steps:
- - setHeader:
- name: CamelAzureStorageBlobBlobName
- constant: overridenName
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: blob
- operation: deleteBlob
- serviceClient: "#client"
- - to:
- uri: mock:result
-----
-====
-
-- `downloadBlobToFile`:
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:start")
- .setHeader("CamelAzureStorageBlobBlobName", constant("overridenName"))
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=downloadBlobToFile&fileDir=/var/mydir&serviceClient=#client")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:start"/>
- <setHeader name="CamelAzureStorageBlobBlobName">
- <constant>overridenName</constant>
- </setHeader>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=blob&operation=downloadBlobToFile&fileDir=/var/mydir&serviceClient=#client"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:start
- steps:
- - setHeader:
- name: CamelAzureStorageBlobBlobName
- constant: overridenName
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: blob
- operation: downloadBlobToFile
- fileDir: /var/mydir
- serviceClient: "#client"
- - to:
- uri: mock:result
-----
-====
-
-- `downloadLink`
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:start")
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=downloadLink&serviceClient=#client")
- .log("My link ${header.CamelAzureStorageBlobDownloadLink}")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:start"/>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=blob&operation=downloadLink&serviceClient=#client"/>
- <log message="My link ${header.CamelAzureStorageBlobDownloadLink}"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:start
- steps:
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: blob
- operation: downloadLink
- serviceClient: "#client"
- - log:
- message: "My link ${header.CamelAzureStorageBlobDownloadLink}"
- - to:
- uri: mock:result
-----
-====
-
-- `uploadBlockBlob`
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:start")
- .setHeader("CamelAzureStorageBlobBlobName", constant("overridenName"))
- .setBody(constant("Block Blob"))
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=uploadBlockBlob&serviceClient=#client")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:start"/>
- <setHeader name="CamelAzureStorageBlobBlobName">
- <constant>overridenName</constant>
- </setHeader>
- <setBody>
- <constant>Block Blob</constant>
- </setBody>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=blob&operation=uploadBlockBlob&serviceClient=#client"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:start
- steps:
- - setHeader:
- name: CamelAzureStorageBlobBlobName
- constant: overridenName
- - setBody:
- constant: Block Blob
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: blob
- operation: uploadBlockBlob
- serviceClient: "#client"
- - to:
- uri: mock:result
-----
-====
-
-- `uploadBlockBlobChunked`
-
-This operation is recommended for uploading large files (larger than 256MB) as
it uses chunked parallel uploads for memory efficiency.
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("file://data?noop=true")
- .log("Uploading file: ${header.CamelFileName}")
-
.toD("azure-storage-blob://camelazure/container1?blobName=${header.CamelFileName}&operation=uploadBlockBlobChunked&blockSize=52428800&maxConcurrency=4&serviceClient=#client")
- .log("Upload completed: ${header.CamelFileName}");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="file://data?noop=true"/>
- <log message="Uploading file: ${header.CamelFileName}"/>
- <toD
uri="azure-storage-blob://camelazure/container1?blobName=${header.CamelFileName}&operation=uploadBlockBlobChunked&blockSize=52428800&maxConcurrency=4&serviceClient=#client"/>
- <log message="Upload completed: ${header.CamelFileName}"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: file://data
- parameters:
- noop: true
- steps:
- - log:
- message: "Uploading file: ${header.CamelFileName}"
- - toD:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: "${header.CamelFileName}"
- operation: uploadBlockBlobChunked
- blockSize: 52428800
- maxConcurrency: 4
- serviceClient: "#client"
- - log:
- message: "Upload completed: ${header.CamelFileName}"
-----
-====
-
-The `blockSize` and `maxConcurrency` options control memory usage and upload
speed:
-
-* `blockSize`: Size of each chunk (default: 4MB, max: 4000MB). Larger blocks =
fewer requests but more memory.
-* `maxConcurrency`: Number of parallel uploads (default: auto-detected based
on CPU cores). Higher = faster but more memory.
-* Memory usage is approximately `blockSize × maxConcurrency`.
-
-- `stageBlockBlobList`
-
-._Java-only: requires BlobBlock objects in the body_
-
-[source,java]
-----
-from("direct:start")
- .process(exchange -> {
- final List<BlobBlock> blocks = new LinkedList<>();
- blocks.add(BlobBlock.createBlobBlock(new
ByteArrayInputStream("Hello".getBytes())));
- blocks.add(BlobBlock.createBlobBlock(new
ByteArrayInputStream("From".getBytes())));
- blocks.add(BlobBlock.createBlobBlock(new
ByteArrayInputStream("Camel".getBytes())));
- exchange.getIn().setBody(blocks);
- })
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=stageBlockBlobList&serviceClient=#client")
- .to("mock:result");
-----
-
-- `commitBlockBlobList`
-
-._Java-only: requires Block objects in the body_
-
-[source,java]
-----
-from("direct:start")
- .process(exchange -> {
- final List<Block> blockIds = new LinkedList<>();
- blockIds.add(new Block().setName("id-1"));
- blockIds.add(new Block().setName("id-2"));
- blockIds.add(new Block().setName("id-3"));
- exchange.getIn().setBody(blockIds);
- })
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=commitBlockBlobList&serviceClient=#client")
- .to("mock:result");
-----
-
-- `getBlobBlockList`
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:start")
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=getBlobBlockList&serviceClient=#client")
- .log("${body}")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:start"/>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=blob&operation=getBlobBlockList&serviceClient=#client"/>
- <log message="${body}"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:start
- steps:
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: blob
- operation: getBlobBlockList
- serviceClient: "#client"
- - log:
- message: "${body}"
- - to:
- uri: mock:result
-----
-====
-
-
-- `createAppendBlob`
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:start")
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=createAppendBlob&serviceClient=#client")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:start"/>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=blob&operation=createAppendBlob&serviceClient=#client"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:start
- steps:
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: blob
- operation: createAppendBlob
- serviceClient: "#client"
- - to:
- uri: mock:result
-----
-====
-
-- `commitAppendBlob`
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:start")
- .setBody(constant("Hello world from my awesome tests!"))
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=commitAppendBlob&serviceClient=#client")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:start"/>
- <setBody>
- <constant>Hello world from my awesome tests!</constant>
- </setBody>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=blob&operation=commitAppendBlob&serviceClient=#client"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:start
- steps:
- - setBody:
- constant: "Hello world from my awesome tests!"
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: blob
- operation: commitAppendBlob
- serviceClient: "#client"
- - to:
- uri: mock:result
-----
-====
-
-- `createPageBlob`
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:start")
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=createPageBlob&serviceClient=#client")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:start"/>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=blob&operation=createPageBlob&serviceClient=#client"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:start
- steps:
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: blob
- operation: createPageBlob
- serviceClient: "#client"
- - to:
- uri: mock:result
-----
-====
-
-- `uploadPageBlob`
-
-NOTE: The `CamelAzureStorageBlobPageBlobRange` header requires a `PageRange`
object, which must be set from a bean or processor.
-
-._Java-only: requires PageRange object_
-
-[source,java]
-----
-from("direct:start")
- .process(exchange -> {
- byte[] dataBytes = new byte[512];
- new Random().nextBytes(dataBytes);
- final InputStream dataStream = new ByteArrayInputStream(dataBytes);
- final PageRange pageRange = new PageRange().setStart(0).setEnd(511);
- exchange.getIn().setHeader("CamelAzureStorageBlobPageBlobRange",
pageRange);
- exchange.getIn().setBody(dataStream);
- })
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=uploadPageBlob&serviceClient=#client")
- .to("mock:result");
-----
-
-- `resizePageBlob`
-
-._Java-only: requires PageRange object_
-
-[source,java]
-----
-from("direct:start")
- .process(exchange -> {
- exchange.getIn().setHeader("CamelAzureStorageBlobPageBlobRange",
- new PageRange().setStart(0).setEnd(511));
- })
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=resizePageBlob&serviceClient=#client")
- .to("mock:result");
-----
-
-- `clearPageBlob`
-
-._Java-only: requires PageRange object_
-
-[source,java]
-----
-from("direct:start")
- .process(exchange -> {
- exchange.getIn().setHeader("CamelAzureStorageBlobPageBlobRange",
- new PageRange().setStart(0).setEnd(511));
- })
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=clearPageBlob&serviceClient=#client")
- .to("mock:result");
-----
-
-- `getPageBlobRanges`
-
-._Java-only: requires PageRange object_
-
-[source,java]
-----
-from("direct:start")
- .process(exchange -> {
- exchange.getIn().setHeader("CamelAzureStorageBlobPageBlobRange",
- new PageRange().setStart(0).setEnd(511));
- })
-
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=getPageBlobRanges&serviceClient=#client")
- .log("${body}")
- .to("mock:result");
-----
-
-- `copyBlob`
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:copyBlob")
- .setHeader("CamelAzureStorageBlobBlobName", constant("file.txt"))
- .setHeader("CamelAzureStorageBlobSourceBlobContainerName",
constant("containerblob1"))
- .setHeader("CamelAzureStorageBlobSourceBlobAccountName",
constant("account"))
-
.to("azure-storage-blob://account/containerblob2?operation=copyBlob&sourceBlobAccessKey=RAW(accessKey)")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:copyBlob"/>
- <setHeader name="CamelAzureStorageBlobBlobName">
- <constant>file.txt</constant>
- </setHeader>
- <setHeader name="CamelAzureStorageBlobSourceBlobContainerName">
- <constant>containerblob1</constant>
- </setHeader>
- <setHeader name="CamelAzureStorageBlobSourceBlobAccountName">
- <constant>account</constant>
- </setHeader>
- <to
uri="azure-storage-blob://account/containerblob2?operation=copyBlob&sourceBlobAccessKey=RAW(accessKey)"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:copyBlob
- steps:
- - setHeader:
- name: CamelAzureStorageBlobBlobName
- constant: file.txt
- - setHeader:
- name: CamelAzureStorageBlobSourceBlobContainerName
- constant: containerblob1
- - setHeader:
- name: CamelAzureStorageBlobSourceBlobAccountName
- constant: account
- - to:
- uri: azure-storage-blob://account/containerblob2
- parameters:
- operation: copyBlob
- sourceBlobAccessKey: "RAW(accessKey)"
- - to:
- uri: mock:result
-----
-====
-
-In this way the `file.txt` in the container `containerblob1` of the account
`account`, will be copied to the container `containerblob2` of the same account.
-
-- `createBlobSnapshot`
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:createBlobSnapshot")
-
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=createBlobSnapshot&serviceClient=#client")
- .log("Snapshot ID: ${header.CamelAzureStorageBlobSnapshotId}")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:createBlobSnapshot"/>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=createBlobSnapshot&serviceClient=#client"/>
- <log message="Snapshot ID: ${header.CamelAzureStorageBlobSnapshotId}"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:createBlobSnapshot
- steps:
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: hello.txt
- operation: createBlobSnapshot
- serviceClient: "#client"
- - log:
- message: "Snapshot ID: ${header.CamelAzureStorageBlobSnapshotId}"
- - to:
- uri: mock:result
-----
-====
-
-=== Reading a specific blob snapshot
-
-The `getBlob`, `downloadBlobToFile` and `downloadLink` operations can target a
specific snapshot by setting the
-`snapshotId` URI parameter or the `CamelAzureStorageBlobSnapshotId` exchange
header. When set, the read is scoped
-to the snapshot version of the blob instead of the live one. The header takes
precedence over the URI parameter.
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:readSnapshot")
- .setHeader("CamelAzureStorageBlobSnapshotId",
constant("2026-04-15T10:00:00.0000000Z"))
-
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=getBlob&serviceClient=#client")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:readSnapshot"/>
- <setHeader name="CamelAzureStorageBlobSnapshotId">
- <constant>2026-04-15T10:00:00.0000000Z</constant>
- </setHeader>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=getBlob&serviceClient=#client"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:readSnapshot
- steps:
- - setHeader:
- name: CamelAzureStorageBlobSnapshotId
- constant: "2026-04-15T10:00:00.0000000Z"
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: hello.txt
- operation: getBlob
- serviceClient: "#client"
- - to:
- uri: mock:result
-----
-====
-
-=== Reading a specific blob version
-
-When blob versioning is enabled on the storage account, the `getBlob`,
`downloadBlobToFile` and `downloadLink`
-operations can target a specific version by setting the `versionId` URI
parameter or the
-`CamelAzureStorageBlobVersionId` exchange header. When set, the read is scoped
to the version of the blob instead
-of the live one. The header takes precedence over the URI parameter.
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:readVersion")
- .setHeader("CamelAzureStorageBlobVersionId",
constant("2026-04-15T10:00:00.0000000Z"))
-
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=getBlob&serviceClient=#client")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:readVersion"/>
- <setHeader name="CamelAzureStorageBlobVersionId">
- <constant>2026-04-15T10:00:00.0000000Z</constant>
- </setHeader>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=getBlob&serviceClient=#client"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:readVersion
- steps:
- - setHeader:
- name: CamelAzureStorageBlobVersionId
- constant: "2026-04-15T10:00:00.0000000Z"
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: hello.txt
- operation: getBlob
- serviceClient: "#client"
- - to:
- uri: mock:result
-----
-====
-
-- `setBlobTags`
-
-NOTE: The `CamelAzureStorageBlobTags` header requires a `Map<String, String>`
value, which must be set from a bean or processor in XML/YAML.
-
-._Java-only: Map.of() for blob tags_
-
-[source,java]
-----
-from("direct:setBlobTags")
- .setHeader("CamelAzureStorageBlobTags", constant(Map.of("status",
"quarantine", "category", "document")))
-
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=setBlobTags&serviceClient=#client")
- .to("mock:result");
-----
-
-- `getBlobTags`
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:getBlobTags")
-
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=getBlobTags&serviceClient=#client")
- .log("Tags: ${body}")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:getBlobTags"/>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=getBlobTags&serviceClient=#client"/>
- <log message="Tags: ${body}"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:getBlobTags
- steps:
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: hello.txt
- operation: getBlobTags
- serviceClient: "#client"
- - log:
- message: "Tags: ${body}"
- - to:
- uri: mock:result
-----
-====
-
-- `findBlobsByTags`
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:findBlobsByTags")
- .setHeader("CamelAzureStorageBlobTagFilter", constant("\"Environment\" =
'Production' AND \"Status\" = 'Active'"))
-
.to("azure-storage-blob://camelazure?operation=findBlobsByTags&serviceClient=#client")
- .log("Matching blobs: ${body}")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:findBlobsByTags"/>
- <setHeader name="CamelAzureStorageBlobTagFilter">
- <constant>"Environment" = 'Production' AND "Status" = 'Active'</constant>
- </setHeader>
- <to
uri="azure-storage-blob://camelazure?operation=findBlobsByTags&serviceClient=#client"/>
- <log message="Matching blobs: ${body}"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:findBlobsByTags
- steps:
- - setHeader:
- name: CamelAzureStorageBlobTagFilter
- constant: "\"Environment\" = 'Production' AND \"Status\" =
'Active'"
- - to:
- uri: azure-storage-blob://camelazure
- parameters:
- operation: findBlobsByTags
- serviceClient: "#client"
- - log:
- message: "Matching blobs: ${body}"
- - to:
- uri: mock:result
-----
-====
-
-- `setBlobLegalHold`
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-// place a legal hold on the blob
-from("direct:setLegalHold")
- .setHeader("CamelAzureStorageBlobLegalHold", constant(true))
-
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=setBlobLegalHold&serviceClient=#client")
- .to("mock:result");
-
-// clear the legal hold
-from("direct:clearLegalHold")
- .setHeader("CamelAzureStorageBlobLegalHold", constant(false))
-
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=setBlobLegalHold&serviceClient=#client")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:setLegalHold"/>
- <setHeader name="CamelAzureStorageBlobLegalHold">
- <constant>true</constant>
- </setHeader>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=setBlobLegalHold&serviceClient=#client"/>
- <to uri="mock:result"/>
-</route>
-
-<route>
- <from uri="direct:clearLegalHold"/>
- <setHeader name="CamelAzureStorageBlobLegalHold">
- <constant>false</constant>
- </setHeader>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=setBlobLegalHold&serviceClient=#client"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:setLegalHold
- steps:
- - setHeader:
- name: CamelAzureStorageBlobLegalHold
- constant: true
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: hello.txt
- operation: setBlobLegalHold
- serviceClient: "#client"
- - to:
- uri: mock:result
-
-- route:
- from:
- uri: direct:clearLegalHold
- steps:
- - setHeader:
- name: CamelAzureStorageBlobLegalHold
- constant: false
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: hello.txt
- operation: setBlobLegalHold
- serviceClient: "#client"
- - to:
- uri: mock:result
-----
-====
-
-- `setBlobImmutabilityPolicy`
-
-._Java-only: requires OffsetDateTime and BlobImmutabilityPolicyMode objects_
-
-[source,java]
-----
-from("direct:setImmutabilityPolicy")
- .setHeader("CamelAzureStorageBlobImmutabilityPolicyExpiryTime",
constant(OffsetDateTime.now().plusDays(7)))
- .setHeader("CamelAzureStorageBlobImmutabilityPolicyMode",
constant(BlobImmutabilityPolicyMode.UNLOCKED))
-
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=setBlobImmutabilityPolicy&serviceClient=#client")
- .to("mock:result");
-----
-
-- `undeleteBlob`
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:undeleteBlob")
-
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=undeleteBlob&serviceClient=#client")
- .to("mock:result");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:undeleteBlob"/>
- <to
uri="azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=undeleteBlob&serviceClient=#client"/>
- <to uri="mock:result"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:undeleteBlob
- steps:
- - to:
- uri: azure-storage-blob://camelazure/container1
- parameters:
- blobName: hello.txt
- operation: undeleteBlob
- serviceClient: "#client"
- - to:
- uri: mock:result
-----
-====
-
-- `setBlobTier`
-
-._Java-only: requires AccessTier and RehydratePriority enum objects_
-
-[source,java]
-----
-// move a blob to the COOL tier for less frequent access
-from("direct:moveToCool")
- .setHeader("CamelAzureStorageBlobAccessTier", constant(AccessTier.COOL))
-
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=setBlobTier&serviceClient=#client")
- .to("mock:result");
-
-// rehydrate a blob from ARCHIVE to HOT with high priority
-from("direct:rehydrate")
- .setHeader("CamelAzureStorageBlobAccessTier", constant(AccessTier.HOT))
- .setHeader("CamelAzureStorageBlobRehydratePriority",
constant(RehydratePriority.HIGH))
-
.to("azure-storage-blob://camelazure/container1?blobName=archived.txt&operation=setBlobTier&serviceClient=#client")
- .to("mock:result");
-----
-
-=== Blob modification during download (ConditionNotMet)
-
-When downloading a blob via streaming (the `getBlob` operation or the consumer
without `fileDir`), the Azure SDK reads the blob in chunks.
-On the first chunk it captures the blob's ETag and uses it as an `if-match`
condition on subsequent chunk requests.
-If the blob is modified by another process between chunks (which changes its
ETag), Azure returns HTTP 412 `ConditionNotMet`.
-
-This is by design in the Azure SDK — it ensures read consistency so that you
do not receive half of one version concatenated with half of another.
-The error is more likely with larger blobs and slower network connections
(e.g., in real Azure environments versus local emulators).
-
-Workarounds:
-
-* Use the `fileDir` option so the consumer downloads the blob atomically to a
file via `downloadBlobToFile` instead of streaming.
-* Use blob snapshots — read from an immutable snapshot ID via the `snapshotId`
option or the `CamelAzureStorageBlobSnapshotId` header, which cannot be
modified.
-* Use blob versioning — read from a specific version via the `versionId`
option or the `CamelAzureStorageBlobVersionId` header.
-* Avoid modifying blobs while they are being consumed.
-
-=== SAS Token generation example
-
-SAS Blob Container tokens can be generated programmatically or via Azure UI.
To generate the token with Java code, the following can be done:
-
-._Java-only: programmatic SAS token generation_
-
-[source,java]
-----
-BlobContainerClient blobClient = new BlobContainerClientBuilder()
- .endpoint(String.format("https://%s.blob.core.windows.net/%s",
accountName, accessKey))
- .containerName(containerName)
- .credential(new StorageSharedKeyCredential(accountName, accessKey))
- .buildClient();
-
-OffsetDateTime expiryTime = OffsetDateTime.now().plusDays(1);
-
-BlobContainerSasPermission blobContainerSasPermission = new
BlobContainerSasPermission()
- .setWritePermission(true)
- .setListPermission(true)
- .setCreatePermission(true)
- .setDeletePermission(true)
- .setAddPermission(true)
- .setReadPermission(true);
-
-BlobServiceSasSignatureValues sasSignatureValues = new
BlobServiceSasSignatureValues(expiryTime, blobContainerSasPermission);
-
-return blobClient.generateSas(sasSignatureValues);
-----
-
-The generated SAS token can be then stored to an application.properties file
so that it can be loaded by the Camel route, for example:
-
-[source,properties]
-----
-camel.component.azure-storage-blob.sas-token=MY_TOKEN_HERE
-----
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:uploadBlob")
-
.to("azure-storage-blob://account/containerblob2?operation=uploadBlockBlob&credentialType=AZURE_SAS");
-----
-
-XML::
-+
-[source,xml]
-----
-<route>
- <from uri="direct:uploadBlob"/>
- <to
uri="azure-storage-blob://account/containerblob2?operation=uploadBlockBlob&credentialType=AZURE_SAS"/>
-</route>
-----
-
-YAML::
-+
-[source,yaml]
-----
-- route:
- from:
- uri: direct:uploadBlob
- steps:
- - to:
- uri: azure-storage-blob://account/containerblob2
- parameters:
- operation: uploadBlockBlob
- credentialType: AZURE_SAS
-----
-====
+* xref:others:azure-storage-blob-operations.adoc[Producer Operations] - All
producer operation examples, blob snapshots/versions, and SAS token generation
+* xref:others:azure-storage-blob-consumer.adoc[Consumer Examples] - Consumer
patterns including delete after read and move after read
== Important Development Notes
diff --git
a/components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-consumer.adoc
b/components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-consumer.adoc
new file mode 100644
index 000000000000..a49f9cc45a4a
--- /dev/null
+++
b/components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-consumer.adoc
@@ -0,0 +1,282 @@
+= Azure Storage Blob - Consumer Examples
+:tabs-sync-option:
+
+xref:ROOT:azure-storage-blob-component.adoc[Back to Azure Storage Blob
Component]
+
+== Consumer Examples
+
+To consume a blob into a file using the file component, this can be done like
this:
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("azure-storage-blob://camelazure/container1?blobName=hello.txt&accountName=yourAccountName&accessKey=yourAccessKey")
+ .to("file://blobdirectory");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from
uri="azure-storage-blob://camelazure/container1?blobName=hello.txt&accountName=yourAccountName&accessKey=yourAccessKey"/>
+ <to uri="file://blobdirectory"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: azure-storage-blob://camelazure/container1
+ parameters:
+ blobName: hello.txt
+ accountName: yourAccountName
+ accessKey: yourAccessKey
+ steps:
+ - to:
+ uri: file://blobdirectory
+----
+====
+
+However, you can also write to file directly without using the file component,
you will need to specify `fileDir` folder path to save your blob in your
machine.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("azure-storage-blob://camelazure/container1?blobName=hello.txt&accountName=yourAccountName&accessKey=yourAccessKey&fileDir=/var/to/awesome/dir")
+ .to("mock:results");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from
uri="azure-storage-blob://camelazure/container1?blobName=hello.txt&accountName=yourAccountName&accessKey=yourAccessKey&fileDir=/var/to/awesome/dir"/>
+ <to uri="mock:results"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: azure-storage-blob://camelazure/container1
+ parameters:
+ blobName: hello.txt
+ accountName: yourAccountName
+ accessKey: yourAccessKey
+ fileDir: /var/to/awesome/dir
+ steps:
+ - to:
+ uri: mock:results
+----
+====
+
+Also, the component supports batch consumer, hence you can consume multiple
blobs with only specifying the container name, the consumer will
+return multiple exchanges depending on the number of the blobs in the
container. Example:
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("azure-storage-blob://camelazure/container1?accountName=yourAccountName&accessKey=yourAccessKey&fileDir=/var/to/awesome/dir")
+ .to("mock:results");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from
uri="azure-storage-blob://camelazure/container1?accountName=yourAccountName&accessKey=yourAccessKey&fileDir=/var/to/awesome/dir"/>
+ <to uri="mock:results"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: azure-storage-blob://camelazure/container1
+ parameters:
+ accountName: yourAccountName
+ accessKey: yourAccessKey
+ fileDir: /var/to/awesome/dir
+ steps:
+ - to:
+ uri: mock:results
+----
+====
+
+=== Delete After Read
+
+The consumer supports automatic deletion of blobs after they have been
successfully processed. This is useful when you want to ensure that blobs are
only processed once.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("azure-storage-blob://camelazure/container1?deleteAfterRead=true&accessKey=RAW(yourAccessKey)")
+ .log("Processing blob: ${header.CamelAzureStorageBlobBlobName}")
+ .to("direct:processBlob");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from
uri="azure-storage-blob://camelazure/container1?deleteAfterRead=true&accessKey=RAW(yourAccessKey)"/>
+ <log message="Processing blob: ${header.CamelAzureStorageBlobBlobName}"/>
+ <to uri="direct:processBlob"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: azure-storage-blob://camelazure/container1
+ parameters:
+ deleteAfterRead: true
+ accessKey: "RAW(yourAccessKey)"
+ steps:
+ - log:
+ message: "Processing blob: ${header.CamelAzureStorageBlobBlobName}"
+ - to:
+ uri: direct:processBlob
+----
+====
+
+IMPORTANT: The delete operation is only performed if the Exchange is
successfully committed. If processing fails or a rollback occurs, the blob will
not be deleted and can be reprocessed on the next poll.
+
+NOTE: When `deleteAfterRead` is set to `false` (the default), the same blobs
will be retrieved repeatedly in subsequent polls. In this case, you should use
the Idempotent Consumer EIP to filter out duplicates based on the
`CamelAzureStorageBlobBlobName` header.
+
+=== Move After Read
+
+The consumer can move blobs to a different container after successful
processing. This is useful for archiving processed blobs or implementing a
processing pipeline across containers.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("azure-storage-blob://camelazure/incoming?moveAfterRead=true&destinationContainer=archive&accessKey=RAW(yourAccessKey)")
+ .log("Processing blob: ${header.CamelAzureStorageBlobBlobName}")
+ .to("direct:processBlob");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from
uri="azure-storage-blob://camelazure/incoming?moveAfterRead=true&destinationContainer=archive&accessKey=RAW(yourAccessKey)"/>
+ <log message="Processing blob: ${header.CamelAzureStorageBlobBlobName}"/>
+ <to uri="direct:processBlob"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: azure-storage-blob://camelazure/incoming
+ parameters:
+ moveAfterRead: true
+ destinationContainer: archive
+ accessKey: "RAW(yourAccessKey)"
+ steps:
+ - log:
+ message: "Processing blob: ${header.CamelAzureStorageBlobBlobName}"
+ - to:
+ uri: direct:processBlob
+----
+====
+
+You can also customize the destination blob name by adding a prefix and/or
suffix:
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("azure-storage-blob://camelazure/source?moveAfterRead=true&destinationContainer=archive&prefix=incoming/&removePrefixOnMove=true&destinationBlobPrefix=processed/&destinationBlobSuffix=.done&accessKey=RAW(yourAccessKey)")
+ .log("Processing: ${header.CamelAzureStorageBlobBlobName}")
+ .to("direct:processBlob");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from
uri="azure-storage-blob://camelazure/source?moveAfterRead=true&destinationContainer=archive&prefix=incoming/&removePrefixOnMove=true&destinationBlobPrefix=processed/&destinationBlobSuffix=.done&accessKey=RAW(yourAccessKey)"/>
+ <log message="Processing: ${header.CamelAzureStorageBlobBlobName}"/>
+ <to uri="direct:processBlob"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: azure-storage-blob://camelazure/source
+ parameters:
+ moveAfterRead: true
+ destinationContainer: archive
+ prefix: "incoming/"
+ removePrefixOnMove: true
+ destinationBlobPrefix: "processed/"
+ destinationBlobSuffix: ".done"
+ accessKey: "RAW(yourAccessKey)"
+ steps:
+ - log:
+ message: "Processing: ${header.CamelAzureStorageBlobBlobName}"
+ - to:
+ uri: direct:processBlob
+----
+====
+
+The move operation works as follows:
+
+1. The blob is copied to the destination container with the new name
+2. The original blob is deleted from the source container
+3. Both operations only occur after the Exchange is successfully committed
+
+The following options control the move behavior:
+
+[width="100%",cols="25%,75%",options="header",]
+|===
+|Option |Description
+|`moveAfterRead` |Enable moving blobs after successful processing
+|`destinationContainer` |Target container for moved blobs (required when
`moveAfterRead=true`)
+|`destinationBlobPrefix` |Prefix to add to the blob name in the destination
+|`destinationBlobSuffix` |Suffix to add to the blob name in the destination
+|`removePrefixOnMove` |Remove the source prefix from the blob name before
adding destination prefix
+|===
diff --git
a/components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-operations.adoc
b/components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-operations.adoc
new file mode 100644
index 000000000000..36b1b0565413
--- /dev/null
+++
b/components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-operations.adoc
@@ -0,0 +1,641 @@
+= Azure Storage Blob - Producer Operations
+:tabs-sync-option:
+
+xref:ROOT:azure-storage-blob-component.adoc[Back to Azure Storage Blob
Component]
+
+== Common Pattern
+
+Most producer operations follow the same pattern: set the `operation`
parameter on the endpoint URI,
+optionally override the container or blob name via headers, and send the
exchange.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("direct:start")
+
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=getBlob&serviceClient=#client")
+ .to("mock:result");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from uri="direct:start"/>
+ <to
uri="azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=getBlob&serviceClient=#client"/>
+ <to uri="mock:result"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: direct:start
+ steps:
+ - to:
+ uri: azure-storage-blob://camelazure/container1
+ parameters:
+ blobName: hello.txt
+ operation: getBlob
+ serviceClient: "#client"
+ - to:
+ uri: mock:result
+----
+====
+
+You can override the container name or blob name at runtime using the
`CamelAzureStorageBlobBlobContainerName`
+and `CamelAzureStorageBlobBlobName` headers respectively.
+
+== Operations Reference
+
+The following table lists all available producer operations. Operations marked
*Java-only* require
+programmatic objects (PageRange, BlobBlock, etc.) that cannot be expressed in
XML or YAML.
+
+[width="100%",cols="3,5,3",options="header"]
+|===
+| Operation | Description | Notes
+
+| `listBlobContainers`
+| List all blob containers in the storage account.
+| Java-only (requires `ListBlobContainersOptions`)
+
+| `createBlobContainer`
+| Create a new blob container.
+|
+
+| `deleteBlobContainer`
+| Delete a blob container.
+|
+
+| `listBlobs`
+| List blobs in a container.
+|
+
+| `listBlobVersions`
+| List all versions of blobs in a container. Versioning must be enabled on the
storage account.
+| Use `CamelAzureStorageBlobPrefix` header to filter by blob name
+
+| `getBlob`
+| Download a blob. Returns an `InputStream`, or writes to an `OutputStream` if
one is set in the body.
+| See <<getBlob>>
+
+| `deleteBlob`
+| Delete a blob.
+|
+
+| `downloadBlobToFile`
+| Download a blob directly to a file. Use the `fileDir` option to set the
target directory.
+|
+
+| `downloadLink`
+| Generate a download link for a blob. The link is returned in the
`CamelAzureStorageBlobDownloadLink` header.
+|
+
+| `uploadBlockBlob`
+| Upload a block blob. The body is used as the blob content.
+|
+
+| `uploadBlockBlobChunked`
+| Upload a large file using chunked parallel uploads.
+| See <<uploadBlockBlobChunked>>
+
+| `stageBlockBlobList`
+| Stage a list of blocks for later commit.
+| Java-only (requires `List<BlobBlock>` body)
+
+| `commitBlockBlobList`
+| Commit a list of previously staged blocks.
+| Java-only (requires `List<Block>` body)
+
+| `getBlobBlockList`
+| Get the list of committed and uncommitted blocks for a block blob.
+|
+
+| `createAppendBlob`
+| Create an append blob.
+|
+
+| `commitAppendBlob`
+| Append content to an append blob. The body is used as the content to append.
+|
+
+| `createPageBlob`
+| Create a page blob.
+|
+
+| `uploadPageBlob`
+| Upload data to a page blob at the range specified by the
`CamelAzureStorageBlobPageBlobRange` header.
+| Java-only (requires `PageRange` header)
+
+| `resizePageBlob`
+| Resize a page blob.
+| Java-only (requires `PageRange` header)
+
+| `clearPageBlob`
+| Clear a range of pages in a page blob.
+| Java-only (requires `PageRange` header)
+
+| `getPageBlobRanges`
+| Get the page ranges for a page blob.
+| Java-only (requires `PageRange` header)
+
+| `copyBlob`
+| Copy a blob from one container to another.
+| See <<copyBlob>>
+
+| `createBlobSnapshot`
+| Create a snapshot of a blob. The snapshot ID is returned in the
`CamelAzureStorageBlobSnapshotId` header.
+|
+
+| `setBlobTags`
+| Set index tags on a blob.
+| Java-only (requires `Map<String, String>` via `CamelAzureStorageBlobTags`
header)
+
+| `getBlobTags`
+| Get the index tags of a blob.
+|
+
+| `findBlobsByTags`
+| Find blobs across containers by tag filter expression.
+| See <<findBlobsByTags>>
+
+| `setBlobLegalHold`
+| Place or clear a legal hold on a blob.
+| See <<setBlobLegalHold>>
+
+| `setBlobImmutabilityPolicy`
+| Set an immutability policy on a blob.
+| Java-only (requires `OffsetDateTime` and `BlobImmutabilityPolicyMode`)
+
+| `setBlobTier`
+| Change the access tier of a blob (Hot, Cool, Cold, Archive).
+| See <<setBlobTier>>
+
+| `undeleteBlob`
+| Restore a soft-deleted blob.
+|
+|===
+
+== Operation Details
+
+This section covers operations that require special configuration beyond the
common pattern.
+
+[[getBlob]]
+=== getBlob
+
+You can either set an `OutputStream` in the exchange body and write the data
to it:
+
+._Java-only: programmatic OutputStream handling_
+
+[source,java]
+----
+from("direct:start")
+ .process(exchange -> {
+ exchange.getIn().setHeader("CamelAzureStorageBlobBlobContainerName",
"overridenName");
+ exchange.getIn().setBody(outputStream);
+ })
+
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=getBlob&serviceClient=#client")
+ .to("mock:result");
+----
+
+If you don't set a body, then this operation will give you an `InputStream`
instance which can be processed further downstream.
+
+[[uploadBlockBlobChunked]]
+=== uploadBlockBlobChunked
+
+This operation is recommended for uploading large files (larger than 256MB) as
it uses chunked parallel uploads for memory efficiency.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("file://data?noop=true")
+ .log("Uploading file: ${header.CamelFileName}")
+
.toD("azure-storage-blob://camelazure/container1?blobName=${header.CamelFileName}&operation=uploadBlockBlobChunked&blockSize=52428800&maxConcurrency=4&serviceClient=#client")
+ .log("Upload completed: ${header.CamelFileName}");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from uri="file://data?noop=true"/>
+ <log message="Uploading file: ${header.CamelFileName}"/>
+ <toD
uri="azure-storage-blob://camelazure/container1?blobName=${header.CamelFileName}&operation=uploadBlockBlobChunked&blockSize=52428800&maxConcurrency=4&serviceClient=#client"/>
+ <log message="Upload completed: ${header.CamelFileName}"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: file://data
+ parameters:
+ noop: true
+ steps:
+ - log:
+ message: "Uploading file: ${header.CamelFileName}"
+ - toD:
+ uri: azure-storage-blob://camelazure/container1
+ parameters:
+ blobName: "${header.CamelFileName}"
+ operation: uploadBlockBlobChunked
+ blockSize: 52428800
+ maxConcurrency: 4
+ serviceClient: "#client"
+ - log:
+ message: "Upload completed: ${header.CamelFileName}"
+----
+====
+
+The `blockSize` and `maxConcurrency` options control memory usage and upload
speed:
+
+* `blockSize`: Size of each chunk (default: 4MB, max: 4000MB). Larger blocks =
fewer requests but more memory.
+* `maxConcurrency`: Number of parallel uploads (default: auto-detected based
on CPU cores). Higher = faster but more memory.
+* Memory usage is approximately `blockSize × maxConcurrency`.
+
+=== stageBlockBlobList and commitBlockBlobList
+
+These two operations work together for multi-block uploads. Both require
Java-specific objects in the body.
+
+._Java-only: staging blocks_
+
+[source,java]
+----
+from("direct:start")
+ .process(exchange -> {
+ final List<BlobBlock> blocks = new LinkedList<>();
+ blocks.add(BlobBlock.createBlobBlock(new
ByteArrayInputStream("Hello".getBytes())));
+ blocks.add(BlobBlock.createBlobBlock(new
ByteArrayInputStream("From".getBytes())));
+ blocks.add(BlobBlock.createBlobBlock(new
ByteArrayInputStream("Camel".getBytes())));
+ exchange.getIn().setBody(blocks);
+ })
+
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=stageBlockBlobList&serviceClient=#client")
+ .to("mock:result");
+----
+
+._Java-only: committing blocks_
+
+[source,java]
+----
+from("direct:start")
+ .process(exchange -> {
+ final List<Block> blockIds = new LinkedList<>();
+ blockIds.add(new Block().setName("id-1"));
+ blockIds.add(new Block().setName("id-2"));
+ blockIds.add(new Block().setName("id-3"));
+ exchange.getIn().setBody(blockIds);
+ })
+
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=commitBlockBlobList&serviceClient=#client")
+ .to("mock:result");
+----
+
+=== Page Blob Operations
+
+The `uploadPageBlob`, `resizePageBlob`, `clearPageBlob`, and
`getPageBlobRanges` operations all require a `PageRange` object
+set via the `CamelAzureStorageBlobPageBlobRange` header.
+
+._Java-only: uploading to a page blob_
+
+[source,java]
+----
+from("direct:start")
+ .process(exchange -> {
+ byte[] dataBytes = new byte[512];
+ new Random().nextBytes(dataBytes);
+ final InputStream dataStream = new ByteArrayInputStream(dataBytes);
+ final PageRange pageRange = new PageRange().setStart(0).setEnd(511);
+ exchange.getIn().setHeader("CamelAzureStorageBlobPageBlobRange",
pageRange);
+ exchange.getIn().setBody(dataStream);
+ })
+
.to("azure-storage-blob://camelazure/container1?blobName=blob&operation=uploadPageBlob&serviceClient=#client")
+ .to("mock:result");
+----
+
+The same pattern applies to `resizePageBlob`, `clearPageBlob`, and
`getPageBlobRanges` — set the `PageRange` header
+and change the `operation` parameter.
+
+[[copyBlob]]
+=== copyBlob
+
+To copy a blob between containers (or across accounts), set the source
container and account via headers:
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("direct:copyBlob")
+ .setHeader("CamelAzureStorageBlobBlobName", constant("file.txt"))
+ .setHeader("CamelAzureStorageBlobSourceBlobContainerName",
constant("containerblob1"))
+ .setHeader("CamelAzureStorageBlobSourceBlobAccountName",
constant("account"))
+
.to("azure-storage-blob://account/containerblob2?operation=copyBlob&sourceBlobAccessKey=RAW(accessKey)")
+ .to("mock:result");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from uri="direct:copyBlob"/>
+ <setHeader name="CamelAzureStorageBlobBlobName">
+ <constant>file.txt</constant>
+ </setHeader>
+ <setHeader name="CamelAzureStorageBlobSourceBlobContainerName">
+ <constant>containerblob1</constant>
+ </setHeader>
+ <setHeader name="CamelAzureStorageBlobSourceBlobAccountName">
+ <constant>account</constant>
+ </setHeader>
+ <to
uri="azure-storage-blob://account/containerblob2?operation=copyBlob&sourceBlobAccessKey=RAW(accessKey)"/>
+ <to uri="mock:result"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: direct:copyBlob
+ steps:
+ - setHeader:
+ name: CamelAzureStorageBlobBlobName
+ constant: file.txt
+ - setHeader:
+ name: CamelAzureStorageBlobSourceBlobContainerName
+ constant: containerblob1
+ - setHeader:
+ name: CamelAzureStorageBlobSourceBlobAccountName
+ constant: account
+ - to:
+ uri: azure-storage-blob://account/containerblob2
+ parameters:
+ operation: copyBlob
+ sourceBlobAccessKey: "RAW(accessKey)"
+ - to:
+ uri: mock:result
+----
+====
+
+This copies `file.txt` from `containerblob1` to `containerblob2` within the
same account.
+
+[[findBlobsByTags]]
+=== findBlobsByTags
+
+Use the `CamelAzureStorageBlobTagFilter` header with a tag filter expression
to find blobs across containers:
+
+[source,java]
+----
+from("direct:findBlobsByTags")
+ .setHeader("CamelAzureStorageBlobTagFilter", constant("\"Environment\" =
'Production' AND \"Status\" = 'Active'"))
+
.to("azure-storage-blob://camelazure?operation=findBlobsByTags&serviceClient=#client")
+ .log("Matching blobs: ${body}")
+ .to("mock:result");
+----
+
+[[setBlobLegalHold]]
+=== setBlobLegalHold
+
+Use the `CamelAzureStorageBlobLegalHold` header with `true` to place or
`false` to clear a legal hold:
+
+[source,java]
+----
+from("direct:setLegalHold")
+ .setHeader("CamelAzureStorageBlobLegalHold", constant(true))
+
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=setBlobLegalHold&serviceClient=#client")
+ .to("mock:result");
+----
+
+[[setBlobTier]]
+=== setBlobTier
+
+._Java-only: requires AccessTier and RehydratePriority enum objects_
+
+[source,java]
+----
+// move a blob to the COOL tier for less frequent access
+from("direct:moveToCool")
+ .setHeader("CamelAzureStorageBlobAccessTier", constant(AccessTier.COOL))
+
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=setBlobTier&serviceClient=#client")
+ .to("mock:result");
+
+// rehydrate a blob from ARCHIVE to HOT with high priority
+from("direct:rehydrate")
+ .setHeader("CamelAzureStorageBlobAccessTier", constant(AccessTier.HOT))
+ .setHeader("CamelAzureStorageBlobRehydratePriority",
constant(RehydratePriority.HIGH))
+
.to("azure-storage-blob://camelazure/container1?blobName=archived.txt&operation=setBlobTier&serviceClient=#client")
+ .to("mock:result");
+----
+
+== Reading a Specific Blob Snapshot
+
+The `getBlob`, `downloadBlobToFile` and `downloadLink` operations can target a
specific snapshot by setting the
+`snapshotId` URI parameter or the `CamelAzureStorageBlobSnapshotId` exchange
header. When set, the read is scoped
+to the snapshot version of the blob instead of the live one. The header takes
precedence over the URI parameter.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("direct:readSnapshot")
+ .setHeader("CamelAzureStorageBlobSnapshotId",
constant("2026-04-15T10:00:00.0000000Z"))
+
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=getBlob&serviceClient=#client")
+ .to("mock:result");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from uri="direct:readSnapshot"/>
+ <setHeader name="CamelAzureStorageBlobSnapshotId">
+ <constant>2026-04-15T10:00:00.0000000Z</constant>
+ </setHeader>
+ <to
uri="azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=getBlob&serviceClient=#client"/>
+ <to uri="mock:result"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: direct:readSnapshot
+ steps:
+ - setHeader:
+ name: CamelAzureStorageBlobSnapshotId
+ constant: "2026-04-15T10:00:00.0000000Z"
+ - to:
+ uri: azure-storage-blob://camelazure/container1
+ parameters:
+ blobName: hello.txt
+ operation: getBlob
+ serviceClient: "#client"
+ - to:
+ uri: mock:result
+----
+====
+
+== Reading a Specific Blob Version
+
+When blob versioning is enabled on the storage account, the `getBlob`,
`downloadBlobToFile` and `downloadLink`
+operations can target a specific version by setting the `versionId` URI
parameter or the
+`CamelAzureStorageBlobVersionId` exchange header. When set, the read is scoped
to the version of the blob instead
+of the live one. The header takes precedence over the URI parameter.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("direct:readVersion")
+ .setHeader("CamelAzureStorageBlobVersionId",
constant("2026-04-15T10:00:00.0000000Z"))
+
.to("azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=getBlob&serviceClient=#client")
+ .to("mock:result");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from uri="direct:readVersion"/>
+ <setHeader name="CamelAzureStorageBlobVersionId">
+ <constant>2026-04-15T10:00:00.0000000Z</constant>
+ </setHeader>
+ <to
uri="azure-storage-blob://camelazure/container1?blobName=hello.txt&operation=getBlob&serviceClient=#client"/>
+ <to uri="mock:result"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: direct:readVersion
+ steps:
+ - setHeader:
+ name: CamelAzureStorageBlobVersionId
+ constant: "2026-04-15T10:00:00.0000000Z"
+ - to:
+ uri: azure-storage-blob://camelazure/container1
+ parameters:
+ blobName: hello.txt
+ operation: getBlob
+ serviceClient: "#client"
+ - to:
+ uri: mock:result
+----
+====
+
+== Blob Modification During Download (ConditionNotMet)
+
+When downloading a blob via streaming (the `getBlob` operation or the consumer
without `fileDir`), the Azure SDK reads the blob in chunks.
+On the first chunk it captures the blob's ETag and uses it as an `if-match`
condition on subsequent chunk requests.
+If the blob is modified by another process between chunks (which changes its
ETag), Azure returns HTTP 412 `ConditionNotMet`.
+
+This is by design in the Azure SDK — it ensures read consistency so that you
do not receive half of one version concatenated with half of another.
+The error is more likely with larger blobs and slower network connections
(e.g., in real Azure environments versus local emulators).
+
+Workarounds:
+
+* Use the `fileDir` option so the consumer downloads the blob atomically to a
file via `downloadBlobToFile` instead of streaming.
+* Use blob snapshots — read from an immutable snapshot ID via the `snapshotId`
option or the `CamelAzureStorageBlobSnapshotId` header, which cannot be
modified.
+* Use blob versioning — read from a specific version via the `versionId`
option or the `CamelAzureStorageBlobVersionId` header.
+* Avoid modifying blobs while they are being consumed.
+
+== SAS Token Generation Example
+
+SAS Blob Container tokens can be generated programmatically or via Azure UI.
To generate the token with Java code, the following can be done:
+
+._Java-only: programmatic SAS token generation_
+
+[source,java]
+----
+BlobContainerClient blobClient = new BlobContainerClientBuilder()
+ .endpoint(String.format("https://%s.blob.core.windows.net/%s",
accountName, accessKey))
+ .containerName(containerName)
+ .credential(new StorageSharedKeyCredential(accountName, accessKey))
+ .buildClient();
+
+OffsetDateTime expiryTime = OffsetDateTime.now().plusDays(1);
+
+BlobContainerSasPermission blobContainerSasPermission = new
BlobContainerSasPermission()
+ .setWritePermission(true)
+ .setListPermission(true)
+ .setCreatePermission(true)
+ .setDeletePermission(true)
+ .setAddPermission(true)
+ .setReadPermission(true);
+
+BlobServiceSasSignatureValues sasSignatureValues = new
BlobServiceSasSignatureValues(expiryTime, blobContainerSasPermission);
+
+return blobClient.generateSas(sasSignatureValues);
+----
+
+The generated SAS token can be then stored to an application.properties file
so that it can be loaded by the Camel route, for example:
+
+[source,properties]
+----
+camel.component.azure-storage-blob.sas-token=MY_TOKEN_HERE
+----
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("direct:uploadBlob")
+
.to("azure-storage-blob://account/containerblob2?operation=uploadBlockBlob&credentialType=AZURE_SAS");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from uri="direct:uploadBlob"/>
+ <to
uri="azure-storage-blob://account/containerblob2?operation=uploadBlockBlob&credentialType=AZURE_SAS"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: direct:uploadBlob
+ steps:
+ - to:
+ uri: azure-storage-blob://account/containerblob2
+ parameters:
+ operation: uploadBlockBlob
+ credentialType: AZURE_SAS
+----
+====
diff --git a/docs/components/modules/others/nav.adoc
b/docs/components/modules/others/nav.adoc
index 95d278de07c6..465a97ea05b9 100644
--- a/docs/components/modules/others/nav.adoc
+++ b/docs/components/modules/others/nav.adoc
@@ -7,6 +7,8 @@
** xref:aws2-s3-producer-operations.adoc[AWS S3 - Producer Operations]
** xref:aws2-s3-streaming.adoc[AWS S3 - Streaming Upload]
*** xref:azure-schema-registry.adoc[Azure Schema Registry]
+** xref:azure-storage-blob-consumer.adoc[Azure Storage Blob - Consumer
Examples]
+** xref:azure-storage-blob-operations.adoc[Azure Storage Blob - Producer
Operations]
** xref:camel-yaml-dsl-validator-maven-plugin.adoc[Camel YAML DSL Validator
Maven Plugin]
** xref:cli-connector.adoc[CLI Connector]
** xref:cli-debug.adoc[CLI Debug]
diff --git
a/docs/components/modules/others/pages/azure-storage-blob-consumer.adoc
b/docs/components/modules/others/pages/azure-storage-blob-consumer.adoc
new file mode 120000
index 000000000000..6f430bda17db
--- /dev/null
+++ b/docs/components/modules/others/pages/azure-storage-blob-consumer.adoc
@@ -0,0 +1 @@
+../../../../../components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-consumer.adoc
\ No newline at end of file
diff --git
a/docs/components/modules/others/pages/azure-storage-blob-operations.adoc
b/docs/components/modules/others/pages/azure-storage-blob-operations.adoc
new file mode 120000
index 000000000000..b8c2b8eddd08
--- /dev/null
+++ b/docs/components/modules/others/pages/azure-storage-blob-operations.adoc
@@ -0,0 +1 @@
+../../../../../components/camel-azure/camel-azure-storage-blob/src/main/docs/azure-storage-blob-operations.adoc
\ No newline at end of file