01
Read the condition behind the 412
Inspect whether the request uses If-Match or another precondition. A 412 is not interchangeable with a permission error or a network timeout. Confirm the intended blob and the version on which the operation relied.
In our local test, a worker captured an ETag, then a separate write changed the blob. Reusing the old ETag with MatchConditions.IfNotModified rejected the overwrite. A readback confirmed that the competing content remained intact.
02
Keep the ETag tied to the content it describes
If a replacement is computed from downloaded content, retain the ETag returned with that same download. Fetching a newer ETag separately and pairing it with old content can undermine the lost-update protection you intended.
This fragment assumes blob is the client for the intended object and build_replacement implements the application’s merge or update rule. The important contract is that observed_etag belongs to the content used to build the replacement.
from azure.core import MatchConditions
download = blob.download_blob()
original = download.readall()
observed_etag = download.properties.etag
replacement = build_replacement(original)
blob.upload_blob(
replacement, overwrite=True,
etag=observed_etag,
match_condition=MatchConditions.IfNotModified,
)03
Handle a conflict as an application decision
A rejected stale condition should lead to a deliberate choice: report the conflict, fetch and merge current content, or abandon a change whose business preconditions no longer hold. Blindly retrying the old ETag cannot make it match a newer blob.
Do not switch to an unconditional overwrite merely because the conditional request failed. That changes the concurrency contract. Preserve a useful, non-sensitive description of the attempted operation so a reviewer can understand why a merge or retry was appropriate.
from azure.core.exceptions import ResourceModifiedError
try:
attempt_conditional_write()
except ResourceModifiedError:
# Reevaluate against current content or report a conflict.
# Do not silently fall back to an unconditional overwrite.
raise04
Verify both protection and recovery
A good regression test should check more than whether a later write succeeds. Confirm that the stale attempt changed nothing, then verify the intentionally reevaluated result under a current condition.
The emulator test returned 412 for the stale attempt, kept other-writer as the stored content, and accepted a later guarded replacement with the current ETag. The test validates the precondition mechanics; the application remains responsible for the merge semantics.
05
Choose the strategy for the actual workload
Optimistic conditions, leases, and intentional last-writer-wins behavior solve different coordination requirements. Use the strategy your application needs rather than applying a retry recipe to every conflict. Multiple API calls are not automatically one transaction.
Keep the original read version, failure status, and verified outcome in diagnostics without dumping blob contents. If the next request fails for a lease, authorization, or network reason, investigate that separate evidence instead of treating all failures as ETag conflicts.
Know the limits
Reproduced with Azurite and azure-storage-blob 12.31.0 using API version 2023-11-03. A stale ETag returned 412 and preserved the competing bytes; a deliberate subsequent write using the current ETag succeeded. No distributed production workload or lease strategy was tested.
Sources & further reading
A technical publication by CoreTrailKit.
Browse all articles