Optimistic Concurrency Control (OCC) is a strategy used to manage shared resources and prevent "lost updates" or race conditions when multiple users or processes attempt to modify the same resource simultaneously.
As an example, consider systems like Google Cloud IAM, where
the shared resource is an IAM Policyapplied to a resource
(like a Project, Bucket, or Service). To implement OCC, systems typically use a
version number or an etag
(entity tag) field on the resource object.
Introduction to OCC
Imagine two processes, A and B, try to update a shared resource at the same time:
-
Process Areads the current state of the resource.
-
Process Breads the same current state.
-
Process Amodifies its copy and writes it back to the server.
-
Process Bmodifies its copy and writes it back to the server.
Because Process Boverwrites the resource without knowing that Process Aalready changed it, Process A's updates are lost.
OCC solves this by introducing a unique fingerprint which changes every time a
entity is modified. In many systems (like IAM), this is done
using an etag
. The server checks this tag on every write:
-
When you read the resource, the server returns an
etag(a unique fingerprint). -
When you send the modified resource back, you must include the original
etag. -
If the server finds that the stored
etagdoes notmatch theetagyou sent (meaning someone else modified the resource since you read it), the write operation fails with anABORTEDorFAILED_PRECONDITIONerror.
This failure forces the client to retrythe entire process—re-read the new
state, re-apply the changes, and try the write again with the new etag
.
Implement the OCC Loop
The core of the OCC implementation is a while
loop that handles the retry
logic. Set a reasonable maximum number of retries to prevent infinite
loops in cases of high contention.
Steps of the Loop
| Step | Action | Implementation Example |
|---|---|---|
|
Read
|
Fetch the current resource state, including the etag
. |
Policy policy = client.getIamPolicy(resourceName);
|
|
Modify
|
Apply the changes to the local object. | policy = policy.toBuilder().addBinding(newBinding).build();
|
|
Write/Check
|
Attempt to save the modified resource using the old etag
. This action must be inside a try
block. |
try { client.setIamPolicy(resourceName, policy); return policy; } catch (AbortedException e) { // retry loop }
|
|
Success/Retry
|
If the write succeeds, exit the loop. If it fails with a concurrency error, increment the retry counter and continue the loop (go back to the Read step). |
The following file provides a runnable example of how to implement the OCC loop using an IAM policy on a Project resource as the target.
Installation
To use this example, add the following dependency to your pom.xml
:
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-resourcemanager</artifactId>
<version>1.45.0</version>
</dependency>
Example
import
com.google.api.gax.rpc. AbortedException
;
import
com.google.api.gax.rpc. FailedPreconditionException
;
import
com.google.cloud.resourcemanager.v3. ProjectName
;
import
com.google.cloud.resourcemanager.v3. ProjectsClient
;
import
com.google.iam.v1. Binding
;
import
com.google.iam.v1. GetIamPolicyRequest
;
import
com.google.iam.v1. Policy
;
import
com.google.iam.v1. SetIamPolicyRequest
;
import
java.util.ArrayList
;
import
java.util.List
;
public
class
IamOccExample
{
/**
* Executes an Optimistic Concurrency Control (OCC) loop to safely update a resource.
*
* This method demonstrates the core Read-Modify-Write-Retry pattern.
*
* @param projectId The Google Cloud Project ID (e.g., "my-project-123").
* @param role The IAM role to grant (e.g., "roles/storage.objectAdmin").
* @param member The member to add (e.g., "user:user@example.com").
* @param maxRetries The maximum number of times to retry the update.
* @return The successfully updated IAM policy (or null on failure).
*/
public
static
Policy
updateIamPolicyWithOcc
(
String
projectId
,
String
role
,
String
member
,
int
maxRetries
)
throws
Exception
{
// Setup Client
try
(
ProjectsClient
projectsClient
=
ProjectsClient
.
create
())
{
String
projectName
=
ProjectName
.
of
(
projectId
).
toString
();
int
retries
=
0
;
// START OCC LOOP (Read-Modify-Write-Retry)
while
(
retries
<
maxRetries
)
{
try
{
// READ: Get the current policy. This includes the current etag.
System
.
out
.
printf
(
"Attempt %d: Reading current IAM policy for %s...%n"
,
retries
,
projectName
);
GetIamPolicyRequest
getIamPolicyRequest
=
GetIamPolicyRequest
.
newBuilder
()
.
setResource
(
projectName
)
.
build
();
Policy
policy
=
projectsClient
.
getIamPolicy
(
getIamPolicyRequest
);
// MODIFY: Apply the desired changes to the local Policy object.
List<Binding>
bindings
=
new
ArrayList
<> (
policy
.
getBindingsList
());
Binding
targetBinding
=
null
;
int
bindingIndex
=
-
1
;
for
(
int
i
=
0
;
i
<
bindings
.
size
();
i
++
)
{
if
(
bindings
.
get
(
i
).
getRole
().
equals
(
role
))
{
targetBinding
=
bindings
.
get
(
i
);
bindingIndex
=
i
;
break
;
}
}
if
(
targetBinding
!=
null
)
{
if
(
targetBinding
.
getMembersList
().
contains
(
member
))
{
System
.
out
.
printf
(
"Policy for role %s and member %s exists already!%n"
,
role
,
member
);
return
policy
;
}
// Create a new binding based on existing one to add the member
Binding
updatedBinding
=
targetBinding
.
toBuilder
()
.
addMembers
(
member
)
.
build
();
bindings
.
set
(
bindingIndex
,
updatedBinding
);
}
else
{
// Role not found, create a new binding
Binding
newBinding
=
Binding
.
newBuilder
()
.
setRole
(
role
)
.
addMembers
(
member
)
.
build
();
bindings
.
add
(
newBinding
);
}
// The policy builder now contains the modified bindings AND the original etag.
Policy
updatedPolicy
=
policy
.
toBuilder
()
.
clearBindings
()
.
addAllBindings
(
bindings
)
.
build
();
// WRITE/CHECK: Attempt to write the modified policy.
System
.
out
.
printf
(
"Attempt %d: Setting modified IAM policy...%n"
,
retries
);
SetIamPolicyRequest
setIamPolicyRequest
=
SetIamPolicyRequest
.
newBuilder
()
.
setResource
(
projectName
)
.
setPolicy
(
updatedPolicy
)
.
build
();
Policy
resultPolicy
=
projectsClient
.
setIamPolicy
(
setIamPolicyRequest
);
// SUCCESS: If the call succeeds, return the new policy and exit the loop.
System
.
out
.
printf
(
"Successfully updated IAM policy in attempt %d.%n"
,
retries
);
return
resultPolicy
;
}
catch
(
AbortedException
|
FailedPreconditionException
e
)
{
// If the etag is stale (concurrency conflict), this will throw a retryable exception.
retries
++
;
System
.
out
.
printf
(
"Concurrency conflict detected (etag mismatch). Retrying... (%d/%d)%n"
,
retries
,
maxRetries
);
// Exponential backoff (100ms * retry count)
Thread
.
sleep
(
100L
*
retries
);
}
}
// END OCC LOOP
}
System
.
out
.
printf
(
"Failed to update IAM policy after %d attempts due to persistent concurrency conflicts.%n"
,
maxRetries
);
return
null
;
}
}

