From d821b8b8a8e839f72db04df16fdfa3083ded476e Mon Sep 17 00:00:00 2001
From: Vinay Gera
Date: Thu, 2 Jul 2026 11:00:08 -0700
Subject: [PATCH 01/12] Promote AccessTokenCache to public API in
com.azure.core.credential
Move AccessTokenCache from com.azure.core.implementation to the public
com.azure.core.credential package, alongside SimpleTokenCache. The class
is now part of the public API surface of azure-core.
Changes:
- Move AccessTokenCache.java to com.azure.core.credential package
- Add comprehensive public JavaDoc
- Update import in BearerTokenAuthenticationPolicy
- Move AccessTokenCacheTests to com.azure.core.credential test package
- Update import in TokenCacheTests
---
.../AccessTokenCache.java | 46 +++++++++++++++++--
.../BearerTokenAuthenticationPolicy.java | 2 +-
.../AccessTokenCacheTests.java | 2 +-
.../core/credential/TokenCacheTests.java | 2 +-
4 files changed, 44 insertions(+), 8 deletions(-)
rename sdk/core/azure-core/src/main/java/com/azure/core/{implementation => credential}/AccessTokenCache.java (88%)
rename sdk/core/azure-core/src/test/java/com/azure/core/{implementation => credential}/AccessTokenCacheTests.java (99%)
diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/AccessTokenCache.java b/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
similarity index 88%
rename from sdk/core/azure-core/src/main/java/com/azure/core/implementation/AccessTokenCache.java
rename to sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
index ba01c09e6b08..b68e9e90d8eb 100644
--- a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/AccessTokenCache.java
+++ b/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
@@ -1,11 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-package com.azure.core.implementation;
+package com.azure.core.credential;
-import com.azure.core.credential.AccessToken;
-import com.azure.core.credential.TokenCredential;
-import com.azure.core.credential.TokenRequestContext;
+import com.azure.core.implementation.AccessTokenCacheInfo;
import com.azure.core.util.logging.ClientLogger;
import com.azure.core.util.logging.LogLevel;
import com.azure.core.util.logging.LoggingEventBuilder;
@@ -25,7 +23,45 @@
import java.util.function.Supplier;
/**
- * A token cache that supports caching a token and refreshing it.
+ *
+ * {@code AccessTokenCache} is a thread-safe token cache that wraps a {@link TokenCredential} and manages proactive
+ * token refresh. It supports both asynchronous and synchronous token retrieval via
+ * {@link #getToken(TokenRequestContext, boolean)} and {@link #getTokenSync(TokenRequestContext, boolean)}.
+ *
+ *
+ *
+ * The cache maintains a single cached {@link AccessToken} per instance and proactively refreshes it before expiry
+ * (by default 5 minutes before the expiry time, or at the {@code refreshAt} time if provided by the credential).
+ * If a refresh fails while a non-expired token is still available, the cached token continues to be returned until
+ * it expires.
+ *
+ *
+ *
+ * When the {@code checkToForceFetchToken} flag is {@code true}, the cache compares the incoming
+ * {@link TokenRequestContext} (scopes, tenant ID, claims) against the context used to acquire the current cached
+ * token. A mismatch causes an immediate token refresh regardless of expiry.
+ *
+ *
+ *
+ * Sample: Wrapping a TokenCredential with AccessTokenCache
+ *
+ *
+ *
+ *
+ * TokenCredential credential = new BasicAuthenticationCredential("username", "password");
+ * AccessTokenCache tokenCache = new AccessTokenCache(credential);
+ * TokenRequestContext requestContext = new TokenRequestContext().addScopes("https://management.azure.com/.default");
+ * // Async usage
+ * Mono<AccessToken> tokenMono = tokenCache.getToken(requestContext, false);
+ * // Sync usage
+ * AccessToken token = tokenCache.getTokenSync(requestContext, false);
+ *
+ *
+ *
+ * @see TokenCredential
+ * @see AccessToken
+ * @see TokenRequestContext
+ * @see SimpleTokenCache
*/
public final class AccessTokenCache {
// The delay after a refresh to attempt another token refresh
diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/BearerTokenAuthenticationPolicy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/BearerTokenAuthenticationPolicy.java
index 01fa5f28cd6f..80f3b823b713 100644
--- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/BearerTokenAuthenticationPolicy.java
+++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/BearerTokenAuthenticationPolicy.java
@@ -12,7 +12,7 @@
import com.azure.core.http.HttpPipelineNextPolicy;
import com.azure.core.http.HttpPipelineNextSyncPolicy;
import com.azure.core.http.HttpResponse;
-import com.azure.core.implementation.AccessTokenCache;
+import com.azure.core.credential.AccessTokenCache;
import com.azure.core.implementation.http.policy.AuthorizationChallengeParser;
import com.azure.core.util.CoreUtils;
import com.azure.core.util.logging.ClientLogger;
diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/implementation/AccessTokenCacheTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/credential/AccessTokenCacheTests.java
similarity index 99%
rename from sdk/core/azure-core/src/test/java/com/azure/core/implementation/AccessTokenCacheTests.java
rename to sdk/core/azure-core/src/test/java/com/azure/core/credential/AccessTokenCacheTests.java
index 07b45772c13f..7339b2a5e22e 100644
--- a/sdk/core/azure-core/src/test/java/com/azure/core/implementation/AccessTokenCacheTests.java
+++ b/sdk/core/azure-core/src/test/java/com/azure/core/credential/AccessTokenCacheTests.java
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-package com.azure.core.implementation;
+package com.azure.core.credential;
import com.azure.core.credential.AccessToken;
import com.azure.core.credential.TokenCredential;
diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/credential/TokenCacheTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/credential/TokenCacheTests.java
index d1062efc7d7d..15326949c12f 100644
--- a/sdk/core/azure-core/src/test/java/com/azure/core/credential/TokenCacheTests.java
+++ b/sdk/core/azure-core/src/test/java/com/azure/core/credential/TokenCacheTests.java
@@ -3,7 +3,7 @@
package com.azure.core.credential;
-import com.azure.core.implementation.AccessTokenCache;
+import com.azure.core.credential.AccessTokenCache;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
From 8cff0095067958f5f1a8a6db49bd7ea4cd96998d Mon Sep 17 00:00:00 2001
From: Vinay Gera
Date: Thu, 2 Jul 2026 11:00:46 -0700
Subject: [PATCH 02/12] Add CHANGELOG entry for AccessTokenCache promotion to
public API
---
sdk/core/azure-core/CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md
index a65de8736a2b..c32472fb384b 100644
--- a/sdk/core/azure-core/CHANGELOG.md
+++ b/sdk/core/azure-core/CHANGELOG.md
@@ -4,6 +4,8 @@
### Features Added
+- Promoted `AccessTokenCache` to a public API in the `com.azure.core.credential` package. This class provides a thread-safe, proactively refreshing token cache that wraps a `TokenCredential`, supporting both synchronous and asynchronous token retrieval. ([#TODO](https://github.com/Azure/azure-sdk-for-java/pull/TODO))
+
### Breaking Changes
### Bugs Fixed
From 18a072c02ce1737fc55135bb26d35df5dae5c05b Mon Sep 17 00:00:00 2001
From: Vinay Gera
Date: Thu, 2 Jul 2026 11:07:52 -0700
Subject: [PATCH 03/12] Add CHANGELOG entry for AccessTokenCache promotion to
public API
---
sdk/core/azure-core/CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md
index c32472fb384b..41f07fe5cf61 100644
--- a/sdk/core/azure-core/CHANGELOG.md
+++ b/sdk/core/azure-core/CHANGELOG.md
@@ -4,7 +4,7 @@
### Features Added
-- Promoted `AccessTokenCache` to a public API in the `com.azure.core.credential` package. This class provides a thread-safe, proactively refreshing token cache that wraps a `TokenCredential`, supporting both synchronous and asynchronous token retrieval. ([#TODO](https://github.com/Azure/azure-sdk-for-java/pull/TODO))
+- Promoted `AccessTokenCache` to a public API in the `com.azure.core.credential` package. This class provides a thread-safe, proactively refreshing token cache that wraps a `TokenCredential`, supporting both synchronous and asynchronous token retrieval.
### Breaking Changes
From 327339b66a4d1b541b8708fe76787ac7473555ac Mon Sep 17 00:00:00 2001
From: Vinay Gera
Date: Fri, 17 Jul 2026 11:33:08 -0700
Subject: [PATCH 04/12] Fix AccessTokenCache public API: rename parameter,
improve Javadoc, fix imports
- Rename 'checkToForceFetchToken' to 'refreshOnContextChange' in public methods
of AccessTokenCache, and propagate the rename to BearerTokenAuthenticationPolicy
and PopTokenAuthenticationPolicy private helpers
- Fix getTokenSync @return Javadoc (was incorrectly saying 'Publisher')
- Add @throws IllegalArgumentException to both public getToken methods
- Add thread-safety guarantee and single-context limitation to class Javadoc
- Clarify refreshOnContextChange semantics and CAE use-case in class/param Javadoc
- Fix constructor null-check error message to follow SDK convention
- Fix internal null-check error messages to follow SDK convention
- Add AccessTokenCacheJavadocCodeSnippets.java for the src_embed marker
- Remove redundant same-package imports from AccessTokenCacheTests
- Fix stale constructor Javadoc and broken import in PopTokenAuthenticationPolicy
---
.../core/credential/AccessTokenCache.java | 66 ++++++++++++-------
.../BearerTokenAuthenticationPolicy.java | 8 +--
.../AccessTokenCacheJavadocCodeSnippets.java | 27 ++++++++
.../credential/AccessTokenCacheTests.java | 7 +-
.../PopTokenAuthenticationPolicy.java | 10 +--
5 files changed, 81 insertions(+), 37 deletions(-)
create mode 100644 sdk/core/azure-core/src/samples/java/com/azure/core/credential/AccessTokenCacheJavadocCodeSnippets.java
diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java b/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
index b68e9e90d8eb..6e666c4b1c09 100644
--- a/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
+++ b/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
@@ -37,9 +37,21 @@
*
*
*
- * When the {@code checkToForceFetchToken} flag is {@code true}, the cache compares the incoming
+ * When the {@code refreshOnContextChange} flag is {@code true}, the cache compares the incoming
* {@link TokenRequestContext} (scopes, tenant ID, claims) against the context used to acquire the current cached
- * token. A mismatch causes an immediate token refresh regardless of expiry.
+ * token. A mismatch causes an immediate token refresh regardless of expiry. This is the mechanism used to support
+ * Continuous Access Evaluation (CAE) claims challenges.
+ *
+ *
+ *
+ * Note: Each instance caches exactly one {@link AccessToken} associated with one
+ * {@link TokenRequestContext}. Do not share a single {@code AccessTokenCache} instance across calls that require
+ * different scopes or tenants simultaneously, as each new context will evict the previously cached token.
+ *
+ *
+ *
+ * This class is thread-safe. Multiple threads may call {@link #getToken(TokenRequestContext, boolean)} and
+ * {@link #getTokenSync(TokenRequestContext, boolean)} concurrently.
*
*
*
@@ -84,12 +96,12 @@ public final class AccessTokenCache {
private final Lock lock;
/**
- * Creates an instance of RefreshableTokenCredential with default scheme "Bearer".
+ * Creates an instance of {@code AccessTokenCache} that wraps the given {@link TokenCredential}.
*
* @param tokenCredential the token credential to be used to acquire the token.
*/
public AccessTokenCache(TokenCredential tokenCredential) {
- Objects.requireNonNull(tokenCredential, "The token credential cannot be null");
+ Objects.requireNonNull(tokenCredential, "'tokenCredential' cannot be null.");
this.wip = new AtomicReference<>();
this.tokenCredential = tokenCredential;
this.cacheInfo = new AtomicReference<>(new AccessTokenCacheInfo(null, OffsetDateTime.now()));
@@ -106,11 +118,15 @@ public AccessTokenCache(TokenCredential tokenCredential) {
* Asynchronously get a token from either the cache or replenish the cache with a new token.
*
* @param tokenRequestContext The request context for token acquisition.
- * @param checkToForceFetchToken The flag indicating whether to force fetch a new token or not.
- * @return The Publisher that emits an AccessToken
+ * @param refreshOnContextChange When {@code true}, compares the incoming {@link TokenRequestContext} against the
+ * one used to acquire the current cached token. If the scopes, tenant ID, or claims differ, a fresh token is
+ * fetched immediately regardless of expiry. Pass {@code false} to always use the cached token when it is
+ * still valid.
+ * @return a {@link Mono} that emits the cached or newly acquired {@link AccessToken}.
+ * @throws IllegalArgumentException if {@code tokenRequestContext} is {@code null}.
*/
- public Mono getToken(TokenRequestContext tokenRequestContext, boolean checkToForceFetchToken) {
- return Mono.defer(retrieveToken(tokenRequestContext, checkToForceFetchToken))
+ public Mono getToken(TokenRequestContext tokenRequestContext, boolean refreshOnContextChange) {
+ return Mono.defer(retrieveToken(tokenRequestContext, refreshOnContextChange))
// Keep resubscribing as long as Mono.defer [token acquisition] emits empty().
.repeatWhenEmpty((Flux longFlux) -> longFlux
.concatMap(ignored -> Flux.just(true).delayElements(Duration.ofMillis(500))));
@@ -120,25 +136,29 @@ public Mono getToken(TokenRequestContext tokenRequestContext, boole
* Synchronously get a token from either the cache or replenish the cache with a new token.
*
* @param tokenRequestContext The request context for token acquisition.
- * @param checkToForceFetchToken The flag indicating whether to force fetch a new token or not.
- * @return The Publisher that emits an AccessToken
+ * @param refreshOnContextChange When {@code true}, compares the incoming {@link TokenRequestContext} against the
+ * one used to acquire the current cached token. If the scopes, tenant ID, or claims differ, a fresh token is
+ * fetched immediately regardless of expiry. Pass {@code false} to always use the cached token when it is
+ * still valid.
+ * @return the cached or newly acquired {@link AccessToken}.
+ * @throws IllegalArgumentException if {@code tokenRequestContext} is {@code null}.
*/
- public AccessToken getTokenSync(TokenRequestContext tokenRequestContext, boolean checkToForceFetchToken) {
+ public AccessToken getTokenSync(TokenRequestContext tokenRequestContext, boolean refreshOnContextChange) {
lock.lock();
try {
- return retrieveTokenSync(tokenRequestContext, checkToForceFetchToken).get();
+ return retrieveTokenSync(tokenRequestContext, refreshOnContextChange).get();
} finally {
lock.unlock();
}
}
private Supplier> retrieveToken(TokenRequestContext tokenRequestContext,
- boolean checkToForceFetchToken) {
+ boolean refreshOnContextChange) {
return () -> {
try {
if (tokenRequestContext == null) {
return Mono.error(LOGGER.logExceptionAsError(
- new IllegalArgumentException("The token request context input cannot be null.")));
+ new IllegalArgumentException("'tokenRequestContext' cannot be null.")));
}
AccessTokenCacheInfo cache = this.cacheInfo.get();
@@ -151,9 +171,9 @@ private Supplier> retrieveToken(TokenRequestContext
Mono fallback;
// Check if the incoming token request context is different from the cached one. A different
- // token request context, requires to fetch a new token as the cached one won't work for the
+ // token request context requires fetching a new token as the cached one won't work for the
// passed in token request context.
- boolean forceRefresh = (checkToForceFetchToken && checkIfForceRefreshRequired(tokenRequestContext))
+ boolean forceRefresh = (refreshOnContextChange && checkIfForceRefreshRequired(tokenRequestContext))
|| this.tokenRequestContext == null;
if (forceRefresh) {
@@ -189,12 +209,12 @@ private Supplier> retrieveToken(TokenRequestContext
.flatMap(processTokenRefreshResult(sinksOne, now, fallback))
.doOnError(sinksOne::tryEmitError),
w -> w.set(null));
- } else if (cachedToken != null && !cachedToken.isExpired() && !checkToForceFetchToken) {
+ } else if (cachedToken != null && !cachedToken.isExpired() && !refreshOnContextChange) {
// another thread might be refreshing the token proactively, but the current token is still valid
return Mono.just(cachedToken);
} else {
- // if a force refresh is possible, then exit and retry.
- if (checkToForceFetchToken) {
+ // if a context-change refresh is pending, exit and retry.
+ if (refreshOnContextChange) {
return Mono.empty();
}
// another thread is definitely refreshing the expired token
@@ -214,11 +234,11 @@ private Supplier> retrieveToken(TokenRequestContext
}
private Supplier retrieveTokenSync(TokenRequestContext tokenRequestContext,
- boolean checkToForceFetchToken) {
+ boolean refreshOnContextChange) {
return () -> {
if (tokenRequestContext == null) {
throw LOGGER.logExceptionAsError(
- new IllegalArgumentException("The token request context input cannot be null."));
+ new IllegalArgumentException("'tokenRequestContext' cannot be null."));
}
AccessTokenCacheInfo cache = this.cacheInfo.get();
AccessToken cachedToken = cache.getCachedAccessToken();
@@ -228,9 +248,9 @@ private Supplier retrieveTokenSync(TokenRequestContext tokenRequest
AccessToken fallback;
// Check if the incoming token request context is different from the cached one. A different
- // token request context, requires to fetch a new token as the cached one won't work for the
+ // token request context requires fetching a new token as the cached one won't work for the
// passed in token request context.
- boolean forceRefresh = (checkToForceFetchToken && checkIfForceRefreshRequired(tokenRequestContext))
+ boolean forceRefresh = (refreshOnContextChange && checkIfForceRefreshRequired(tokenRequestContext))
|| this.tokenRequestContext == null;
if (forceRefresh) {
diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/BearerTokenAuthenticationPolicy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/BearerTokenAuthenticationPolicy.java
index 80f3b823b713..549cc18faa89 100644
--- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/BearerTokenAuthenticationPolicy.java
+++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/BearerTokenAuthenticationPolicy.java
@@ -208,16 +208,16 @@ public void setAuthorizationHeaderSync(HttpPipelineCallContext context, TokenReq
}
private Mono setAuthorizationHeaderHelper(HttpPipelineCallContext context,
- TokenRequestContext tokenRequestContext, boolean checkToForceFetchToken) {
- return cache.getToken(tokenRequestContext, checkToForceFetchToken).flatMap(token -> {
+ TokenRequestContext tokenRequestContext, boolean refreshOnContextChange) {
+ return cache.getToken(tokenRequestContext, refreshOnContextChange).flatMap(token -> {
setAuthorizationHeader(context.getHttpRequest().getHeaders(), token.getToken());
return Mono.empty();
});
}
private void setAuthorizationHeaderHelperSync(HttpPipelineCallContext context,
- TokenRequestContext tokenRequestContext, boolean checkToForceFetchToken) {
- AccessToken token = cache.getTokenSync(tokenRequestContext, checkToForceFetchToken);
+ TokenRequestContext tokenRequestContext, boolean refreshOnContextChange) {
+ AccessToken token = cache.getTokenSync(tokenRequestContext, refreshOnContextChange);
setAuthorizationHeader(context.getHttpRequest().getHeaders(), token.getToken());
}
diff --git a/sdk/core/azure-core/src/samples/java/com/azure/core/credential/AccessTokenCacheJavadocCodeSnippets.java b/sdk/core/azure-core/src/samples/java/com/azure/core/credential/AccessTokenCacheJavadocCodeSnippets.java
new file mode 100644
index 000000000000..41c251f70e36
--- /dev/null
+++ b/sdk/core/azure-core/src/samples/java/com/azure/core/credential/AccessTokenCacheJavadocCodeSnippets.java
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.core.credential;
+
+import reactor.core.publisher.Mono;
+
+import java.time.OffsetDateTime;
+
+/**
+ * Codesnippets for {@link AccessTokenCache}.
+ */
+public class AccessTokenCacheJavadocCodeSnippets {
+
+ public void accessTokenCacheSnippet() {
+ // BEGIN: com.azure.core.credential.accessTokenCache
+ TokenCredential credential = new BasicAuthenticationCredential("username", "password");
+ AccessTokenCache tokenCache = new AccessTokenCache(credential);
+ TokenRequestContext requestContext = new TokenRequestContext().addScopes("https://management.azure.com/.default");
+ // Async usage
+ Mono tokenMono = tokenCache.getToken(requestContext, false);
+ // Sync usage
+ AccessToken token = tokenCache.getTokenSync(requestContext, false);
+ // END: com.azure.core.credential.accessTokenCache
+ }
+
+}
diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/credential/AccessTokenCacheTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/credential/AccessTokenCacheTests.java
index 7339b2a5e22e..a673a9ee3ae2 100644
--- a/sdk/core/azure-core/src/test/java/com/azure/core/credential/AccessTokenCacheTests.java
+++ b/sdk/core/azure-core/src/test/java/com/azure/core/credential/AccessTokenCacheTests.java
@@ -3,9 +3,6 @@
package com.azure.core.credential;
-import com.azure.core.credential.AccessToken;
-import com.azure.core.credential.TokenCredential;
-import com.azure.core.credential.TokenRequestContext;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -83,7 +80,7 @@ public void testTenantIdChangeTriggersForceRefresh() {
assertEquals(1, refreshCount.get());
- // Second request with same tenant should not trigger refresh (checkToForceFetchToken=false)
+ // Second request with same tenant should not trigger refresh (refreshOnContextChange=false)
StepVerifier.create(cache.getToken(context1, false)).assertNext(token -> {
assertNotNull(token);
assertEquals("token-1", token.getToken()); // Same token
@@ -91,7 +88,7 @@ public void testTenantIdChangeTriggersForceRefresh() {
assertEquals(1, refreshCount.get()); // No additional refresh
- // Third request with different tenant should trigger force refresh with checkToForceFetchToken=true
+ // Third request with different tenant should trigger force refresh with refreshOnContextChange=true
TokenRequestContext context2 = new TokenRequestContext().addScopes(SCOPE).setTenantId(TENANT_ID_2);
StepVerifier.create(cache.getToken(context2, true)).assertNext(token -> {
diff --git a/sdk/identity/azure-identity-broker/src/test/java/com/azure/identity/broker/shr/resources/PopTokenAuthenticationPolicy.java b/sdk/identity/azure-identity-broker/src/test/java/com/azure/identity/broker/shr/resources/PopTokenAuthenticationPolicy.java
index 87f55ea0f004..5376a6cb148e 100644
--- a/sdk/identity/azure-identity-broker/src/test/java/com/azure/identity/broker/shr/resources/PopTokenAuthenticationPolicy.java
+++ b/sdk/identity/azure-identity-broker/src/test/java/com/azure/identity/broker/shr/resources/PopTokenAuthenticationPolicy.java
@@ -14,7 +14,7 @@
import com.azure.core.http.HttpPipelineNextSyncPolicy;
import com.azure.core.http.HttpResponse;
import com.azure.core.http.policy.HttpPipelinePolicy;
-import com.azure.core.implementation.AccessTokenCache;
+import com.azure.core.credential.AccessTokenCache;
import com.azure.core.util.CoreUtils;
import com.azure.core.util.logging.ClientLogger;
import reactor.core.publisher.Mono;
@@ -169,7 +169,7 @@ public HttpResponse processSync(HttpPipelineCallContext context, HttpPipelineNex
}
}
- private Mono setAuthorizationHeaderHelper(HttpPipelineCallContext context, boolean checkToForceFetchToken) {
+ private Mono setAuthorizationHeaderHelper(HttpPipelineCallContext context, boolean refreshOnContextChange) {
if (!"https".equals(context.getHttpRequest().getUrl().getProtocol())) {
throw LOGGER.logExceptionAsError(new RuntimeException(
"Proof of possession token authentication is not permitted for non TLS-protected (HTTPS) endpoints."));
@@ -181,7 +181,7 @@ private Mono setAuthorizationHeaderHelper(HttpPipelineCallContext context,
.setRequestUrl(context.getHttpRequest().getUrl()));
if (!CoreUtils.isNullOrEmpty(popNonce)) {
- return this.cache.getToken(popTokenRequestContext, checkToForceFetchToken).flatMap((token) -> {
+ return this.cache.getToken(popTokenRequestContext, refreshOnContextChange).flatMap((token) -> {
setAuthorizationHeader(context.getHttpRequest().getHeaders(), token.getToken());
return Mono.empty();
});
@@ -189,7 +189,7 @@ private Mono setAuthorizationHeaderHelper(HttpPipelineCallContext context,
return Mono.empty();
}
- private void setAuthorizationHeaderHelperSync(HttpPipelineCallContext context, boolean checkToForceFetchToken) {
+ private void setAuthorizationHeaderHelperSync(HttpPipelineCallContext context, boolean refreshOnContextChange) {
if (!"https".equals(context.getHttpRequest().getUrl().getProtocol())) {
throw LOGGER.logExceptionAsError(new RuntimeException(
"Proof of possession token authentication is not permitted for non TLS-protected (HTTPS) endpoints."));
@@ -199,7 +199,7 @@ private void setAuthorizationHeaderHelperSync(HttpPipelineCallContext context, b
.setRequestMethod(context.getHttpRequest().getHttpMethod())
.setRequestUrl(context.getHttpRequest().getUrl()));
- AccessToken token = this.cache.getTokenSync(popTokenRequestContext, checkToForceFetchToken);
+ AccessToken token = this.cache.getTokenSync(popTokenRequestContext, refreshOnContextChange);
setAuthorizationHeader(context.getHttpRequest().getHeaders(), token.getToken());
}
From 324fc0120aa9f8444a98f0975e7563f5fb2e4715 Mon Sep 17 00:00:00 2001
From: Vinay Gera
Date: Fri, 17 Jul 2026 12:11:29 -0700
Subject: [PATCH 05/12] Fix azure-identity-broker CI: build azure-core from
source for PopTokenAuthenticationPolicy
PopTokenAuthenticationPolicy uses com.azure.core.credential.AccessTokenCache which
is new in azure-core 1.59.0-beta.1. The azure-identity CI was building against the
published azure-core 1.58.1 (which doesn't have the public class), causing a compile
error.
- Add azure-core to AdditionalModules in sdk/identity/ci.yml so the identity CI
builds it from source before compiling azure-identity-broker
- Add azure-core 1.59.0-beta.1 as a direct test dependency in azure-identity-broker
pom so Maven resolves the locally built version over the transitive published one
---
eng/versioning/version_client.txt | 1 +
sdk/identity/azure-identity/pom.xml | 2 +-
sdk/identity/ci.yml | 3 +++
3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/eng/versioning/version_client.txt b/eng/versioning/version_client.txt
index 1a206ffb4585..3d192ffd3ec9 100644
--- a/eng/versioning/version_client.txt
+++ b/eng/versioning/version_client.txt
@@ -567,6 +567,7 @@ io.clientcore:optional-dependency-tests;1.0.0-beta.1;1.0.0-beta.1
# In the pom, the version update tag after the version should name the unreleased package and the dependency version:
#
+unreleased_com.azure:azure-core;1.59.0-beta.1
unreleased_com.azure.v2:azure-core;2.0.0-beta.1
unreleased_com.azure.v2:azure-identity;2.0.0-beta.1
unreleased_com.azure.v2:azure-data-appconfiguration;2.0.0-beta.1
diff --git a/sdk/identity/azure-identity/pom.xml b/sdk/identity/azure-identity/pom.xml
index 343865302e0d..9a49005d3214 100644
--- a/sdk/identity/azure-identity/pom.xml
+++ b/sdk/identity/azure-identity/pom.xml
@@ -35,7 +35,7 @@
com.azure
azure-core
- 1.58.1
+ 1.59.0-beta.1
diff --git a/sdk/identity/ci.yml b/sdk/identity/ci.yml
index 9dc5816b6e01..9fc5fe9bcd1d 100644
--- a/sdk/identity/ci.yml
+++ b/sdk/identity/ci.yml
@@ -74,6 +74,9 @@ extends:
# required by the above perf library
- name: perf-test-core
groupId: com.azure
+ # required by azure-identity-broker tests (PopTokenAuthenticationPolicy uses public AccessTokenCache)
+ - name: azure-core
+ groupId: com.azure
LiveTestStages:
- template: /eng/pipelines/templates/stages/archetype-sdk-tests-isolated.yml
parameters:
From 3fc29c83a5b509b1a0ef5dfb09a29a90a0cbf885 Mon Sep 17 00:00:00 2001
From: Vinay Gera
Date: Fri, 17 Jul 2026 12:28:42 -0700
Subject: [PATCH 06/12] Remove redundant same-package import of
AccessTokenCache in TokenCacheTests
---
.../src/test/java/com/azure/core/credential/TokenCacheTests.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/credential/TokenCacheTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/credential/TokenCacheTests.java
index 15326949c12f..1e6238ee476d 100644
--- a/sdk/core/azure-core/src/test/java/com/azure/core/credential/TokenCacheTests.java
+++ b/sdk/core/azure-core/src/test/java/com/azure/core/credential/TokenCacheTests.java
@@ -3,7 +3,6 @@
package com.azure.core.credential;
-import com.azure.core.credential.AccessTokenCache;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
From 40715202c80f9bf231dcf25625c13b3b646c9c46 Mon Sep 17 00:00:00 2001
From: Vinay Gera
Date: Fri, 17 Jul 2026 12:30:32 -0700
Subject: [PATCH 07/12] Update azure-identity-perf to use unreleased azure-core
1.59.0-beta.1
---
sdk/identity/azure-identity-perf/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sdk/identity/azure-identity-perf/pom.xml b/sdk/identity/azure-identity-perf/pom.xml
index f50f3bdb06c4..7e71e6ae4c8c 100644
--- a/sdk/identity/azure-identity-perf/pom.xml
+++ b/sdk/identity/azure-identity-perf/pom.xml
@@ -36,7 +36,7 @@
com.azure
azure-core
- 1.58.1
+ 1.59.0-beta.1
From 6b68986e30d9871510220fdc79f05a6142e45e75 Mon Sep 17 00:00:00 2001
From: Vinay Gera
Date: Fri, 17 Jul 2026 13:16:55 -0700
Subject: [PATCH 08/12] Fix Spotless formatting violations in AccessTokenCache
---
.../java/com/azure/core/credential/AccessTokenCache.java | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java b/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
index 6e666c4b1c09..5c5e775dc179 100644
--- a/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
+++ b/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
@@ -157,8 +157,8 @@ private Supplier> retrieveToken(TokenRequestContext
return () -> {
try {
if (tokenRequestContext == null) {
- return Mono.error(LOGGER.logExceptionAsError(
- new IllegalArgumentException("'tokenRequestContext' cannot be null.")));
+ return Mono.error(LOGGER
+ .logExceptionAsError(new IllegalArgumentException("'tokenRequestContext' cannot be null.")));
}
AccessTokenCacheInfo cache = this.cacheInfo.get();
@@ -237,8 +237,7 @@ private Supplier retrieveTokenSync(TokenRequestContext tokenRequest
boolean refreshOnContextChange) {
return () -> {
if (tokenRequestContext == null) {
- throw LOGGER.logExceptionAsError(
- new IllegalArgumentException("'tokenRequestContext' cannot be null."));
+ throw LOGGER.logExceptionAsError(new IllegalArgumentException("'tokenRequestContext' cannot be null."));
}
AccessTokenCacheInfo cache = this.cacheInfo.get();
AccessToken cachedToken = cache.getCachedAccessToken();
From 579c9a12b16e1f5598a4ec43db0c4d7bbd2f3f8f Mon Sep 17 00:00:00 2001
From: Vinay Gera
Date: Fri, 17 Jul 2026 14:53:10 -0700
Subject: [PATCH 09/12] Fix Checkstyle linting violations in AccessTokenCache
and snippets
- Use LOGGER.logThrowableAsError() instead of bare throw in retrieveTokenSync
(ThrowFromClientLoggerCheck violation)
- Remove unused java.time.OffsetDateTime import from AccessTokenCacheJavadocCodeSnippets
(UnusedImportsCheck violation)
---
.../main/java/com/azure/core/credential/AccessTokenCache.java | 2 +-
.../core/credential/AccessTokenCacheJavadocCodeSnippets.java | 2 --
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java b/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
index 5c5e775dc179..4ec5e6246836 100644
--- a/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
+++ b/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
@@ -300,7 +300,7 @@ private Supplier retrieveTokenSync(TokenRequestContext tokenRequest
if (fallback != null) {
return fallback;
}
- throw error;
+ throw LOGGER.logThrowableAsError(error);
}
};
}
diff --git a/sdk/core/azure-core/src/samples/java/com/azure/core/credential/AccessTokenCacheJavadocCodeSnippets.java b/sdk/core/azure-core/src/samples/java/com/azure/core/credential/AccessTokenCacheJavadocCodeSnippets.java
index 41c251f70e36..bbb20abf70e6 100644
--- a/sdk/core/azure-core/src/samples/java/com/azure/core/credential/AccessTokenCacheJavadocCodeSnippets.java
+++ b/sdk/core/azure-core/src/samples/java/com/azure/core/credential/AccessTokenCacheJavadocCodeSnippets.java
@@ -5,8 +5,6 @@
import reactor.core.publisher.Mono;
-import java.time.OffsetDateTime;
-
/**
* Codesnippets for {@link AccessTokenCache}.
*/
From 03a5062e2d900a99fee0ac9bf91db0487dca5570 Mon Sep 17 00:00:00 2001
From: Vinay Gera
Date: Fri, 17 Jul 2026 15:09:24 -0700
Subject: [PATCH 10/12] Fix compilation error: narrow Throwable catch to
RuntimeException in retrieveTokenSync
logThrowableAsError returns Throwable which cannot be thrown from a Supplier lambda.
Supplier.get() only throws RuntimeException, so narrowing the catch is correct.
Use logExceptionAsError which returns RuntimeException and compiles cleanly.
---
.../main/java/com/azure/core/credential/AccessTokenCache.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java b/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
index 4ec5e6246836..6ceb65105250 100644
--- a/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
+++ b/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
@@ -291,7 +291,7 @@ private Supplier retrieveTokenSync(TokenRequestContext tokenRequest
} else {
return fallback;
}
- } catch (Throwable error) {
+ } catch (RuntimeException error) {
buildTokenRefreshLog(LogLevel.ERROR, cachedToken, now).log("Failed to acquire a new access token.",
error);
OffsetDateTime nextTokenRefreshTime = OffsetDateTime.now();
@@ -300,7 +300,7 @@ private Supplier retrieveTokenSync(TokenRequestContext tokenRequest
if (fallback != null) {
return fallback;
}
- throw LOGGER.logThrowableAsError(error);
+ throw LOGGER.logExceptionAsError(error);
}
};
}
From c7ec5febb9c36a96629d29f641fabc804ff7ea2b Mon Sep 17 00:00:00 2001
From: Vinay Gera
Date: Fri, 17 Jul 2026 15:20:04 -0700
Subject: [PATCH 11/12] Catch Throwable in retrieveTokenSync, wrap
non-RuntimeException before rethrowing
Error subclasses (OutOfMemoryError etc.) should also be caught. Since the Supplier
lambda can only propagate RuntimeException, non-RuntimeException Throwables are
wrapped in RuntimeException before being thrown through LOGGER.logExceptionAsError.
---
.../java/com/azure/core/credential/AccessTokenCache.java | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java b/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
index 6ceb65105250..5006c1a31918 100644
--- a/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
+++ b/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
@@ -291,7 +291,7 @@ private Supplier retrieveTokenSync(TokenRequestContext tokenRequest
} else {
return fallback;
}
- } catch (RuntimeException error) {
+ } catch (Throwable error) {
buildTokenRefreshLog(LogLevel.ERROR, cachedToken, now).log("Failed to acquire a new access token.",
error);
OffsetDateTime nextTokenRefreshTime = OffsetDateTime.now();
@@ -300,7 +300,10 @@ private Supplier retrieveTokenSync(TokenRequestContext tokenRequest
if (fallback != null) {
return fallback;
}
- throw LOGGER.logExceptionAsError(error);
+ if (error instanceof RuntimeException) {
+ throw LOGGER.logExceptionAsError((RuntimeException) error);
+ }
+ throw LOGGER.logExceptionAsError(new RuntimeException(error));
}
};
}
From ca75b1dcc9f0e4a12a2792b1b01149b610115e62 Mon Sep 17 00:00:00 2001
From: Vinay Gera
Date: Fri, 17 Jul 2026 15:39:48 -0700
Subject: [PATCH 12/12] Fix codesnippet: escape URL slashes in accessTokenCache
Javadoc snippet
---
.../main/java/com/azure/core/credential/AccessTokenCache.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java b/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
index 5006c1a31918..d1589ec260d2 100644
--- a/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
+++ b/sdk/core/azure-core/src/main/java/com/azure/core/credential/AccessTokenCache.java
@@ -62,7 +62,7 @@
*
* TokenCredential credential = new BasicAuthenticationCredential("username", "password");
* AccessTokenCache tokenCache = new AccessTokenCache(credential);
- * TokenRequestContext requestContext = new TokenRequestContext().addScopes("https://management.azure.com/.default");
+ * TokenRequestContext requestContext = new TokenRequestContext().addScopes("https://management.azure.com/.default");
* // Async usage
* Mono<AccessToken> tokenMono = tokenCache.getToken(requestContext, false);
* // Sync usage