From 130d84ef576bdaac0a4e5f26c0e46dcbbea45534 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Fri, 27 Mar 2026 10:15:18 -0400 Subject: [PATCH 1/4] chore(spanner): add example for Mutable Credentials --- .../spanner/MutableCredentialsExample.java | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java diff --git a/java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java b/java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java new file mode 100644 index 000000000000..77098cb5710d --- /dev/null +++ b/java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java @@ -0,0 +1,99 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.spanner; + +// [START spanner_mutable_credentials] + +import com.google.auth.oauth2.ServiceAccountCredentials; +import com.google.cloud.spanner.MutableCredentials; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.FileTime; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +public class MutableCredentialsExample { + + static void createClientWithMutableCredentials() throws IOException { + final String credentialsPath = "location_of_service_account_credential_json"; + Path path = Paths.get(credentialsPath); + // Use an AtomicReference to hold the mutable lastModifiedTime so it can be accessed in the lambda + final java.util.concurrent.atomic.AtomicReference lastModifiedTime = + new java.util.concurrent.atomic.AtomicReference<>(Files.getLastModifiedTime(path)); + + // 1 - create service account credentials + ServiceAccountCredentials serviceAccountCredentials; + try (FileInputStream is = new FileInputStream(credentialsPath)) { + serviceAccountCredentials = ServiceAccountCredentials.fromStream(is); + } + + // 2 - wrap credentials from step 1 in a MutableCredentials instance + MutableCredentials mutableCredentials = new MutableCredentials(serviceAccountCredentials); + + // 3 - set credentials on your SpannerOptions builder to your mutableCredentials + SpannerOptions options = SpannerOptions.newBuilder().setCredentials(mutableCredentials).build(); + + // 4 - include logic for when/how to update your mutableCredentials + // In this example we'll use a SchedulerExecutorService to periodically check for updates + ThreadFactory daemonThreadFactory = + runnable -> { + Thread thread = new Thread(runnable, "spanner-mutable-credentials-rotator"); + thread.setDaemon(true); + return thread; + }; + ScheduledExecutorService executorService = + Executors.newSingleThreadScheduledExecutor(daemonThreadFactory); + executorService.scheduleAtFixedRate( + () -> { + try { + FileTime currentModifiedTime = Files.getLastModifiedTime(path); + if (currentModifiedTime.compareTo(lastModifiedTime.get()) > 0) { + lastModifiedTime.set(currentModifiedTime); + try (FileInputStream is = new FileInputStream(credentialsPath)) { + ServiceAccountCredentials credentials = ServiceAccountCredentials.fromStream(is); + mutableCredentials.updateCredentials(credentials); + } + } + } catch (IOException e) { + System.err.println("Failed to check or update credentials: " + e.getMessage()); + } + }, + 15, + 15, + TimeUnit.MINUTES); + + // 5. Use the client + try (Spanner spanner = options.getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + // Perform operations... + // long running client operations will always use the latest credentials wrapped in + // mutableCredentials + } finally { + // Ensure the executor is shut down when the application exits or the client is closed + executorService.shutdown(); + } + } +} +// [END spanner_mutable_credentials] From bf7fae6ab4e769eaed848596f63c85313cb03ad2 Mon Sep 17 00:00:00 2001 From: ldetmer <1771267+ldetmer@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:36:57 -0400 Subject: [PATCH 2/4] fix long import Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../java/com/example/spanner/MutableCredentialsExample.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java b/java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java index 77098cb5710d..bacc19410b96 100644 --- a/java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java +++ b/java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java @@ -40,8 +40,8 @@ static void createClientWithMutableCredentials() throws IOException { final String credentialsPath = "location_of_service_account_credential_json"; Path path = Paths.get(credentialsPath); // Use an AtomicReference to hold the mutable lastModifiedTime so it can be accessed in the lambda - final java.util.concurrent.atomic.AtomicReference lastModifiedTime = - new java.util.concurrent.atomic.AtomicReference<>(Files.getLastModifiedTime(path)); + final AtomicReference lastModifiedTime = + new AtomicReference<>(Files.getLastModifiedTime(path)); // 1 - create service account credentials ServiceAccountCredentials serviceAccountCredentials; From e424c32f6625876729822b5b941e832bafe961b8 Mon Sep 17 00:00:00 2001 From: ldetmer <1771267+ldetmer@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:47:26 -0400 Subject: [PATCH 3/4] move credentialsPath to input arg Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../java/com/example/spanner/MutableCredentialsExample.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java b/java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java index bacc19410b96..5d8114d37139 100644 --- a/java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java +++ b/java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java @@ -36,8 +36,7 @@ public class MutableCredentialsExample { - static void createClientWithMutableCredentials() throws IOException { - final String credentialsPath = "location_of_service_account_credential_json"; + static void createClientWithMutableCredentials(String credentialsPath) throws IOException { Path path = Paths.get(credentialsPath); // Use an AtomicReference to hold the mutable lastModifiedTime so it can be accessed in the lambda final AtomicReference lastModifiedTime = From b0d350490a4ab12bce374e3a597625fe98dc4ef0 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Fri, 27 Mar 2026 13:30:37 -0400 Subject: [PATCH 4/4] format fix # Conflicts: # java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java --- .../spanner/MutableCredentialsExample.java | 105 +++++++++--------- 1 file changed, 53 insertions(+), 52 deletions(-) diff --git a/java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java b/java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java index 5d8114d37139..fa6171180201 100644 --- a/java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java +++ b/java-spanner/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java @@ -36,63 +36,64 @@ public class MutableCredentialsExample { - static void createClientWithMutableCredentials(String credentialsPath) throws IOException { - Path path = Paths.get(credentialsPath); - // Use an AtomicReference to hold the mutable lastModifiedTime so it can be accessed in the lambda - final AtomicReference lastModifiedTime = - new AtomicReference<>(Files.getLastModifiedTime(path)); + static void createClientWithMutableCredentials(String credentialsPath) throws IOException { + Path path = Paths.get(credentialsPath); + // Use an AtomicReference to hold the mutable lastModifiedTime so it can be accessed in the + // lambda + final AtomicReference lastModifiedTime = + new AtomicReference<>(Files.getLastModifiedTime(path)); - // 1 - create service account credentials - ServiceAccountCredentials serviceAccountCredentials; - try (FileInputStream is = new FileInputStream(credentialsPath)) { - serviceAccountCredentials = ServiceAccountCredentials.fromStream(is); - } + // 1 - create service account credentials + ServiceAccountCredentials serviceAccountCredentials; + try (FileInputStream is = new FileInputStream(credentialsPath)) { + serviceAccountCredentials = ServiceAccountCredentials.fromStream(is); + } - // 2 - wrap credentials from step 1 in a MutableCredentials instance - MutableCredentials mutableCredentials = new MutableCredentials(serviceAccountCredentials); + // 2 - wrap credentials from step 1 in a MutableCredentials instance + MutableCredentials mutableCredentials = new MutableCredentials(serviceAccountCredentials); - // 3 - set credentials on your SpannerOptions builder to your mutableCredentials - SpannerOptions options = SpannerOptions.newBuilder().setCredentials(mutableCredentials).build(); + // 3 - set credentials on your SpannerOptions builder to your mutableCredentials + SpannerOptions options = SpannerOptions.newBuilder().setCredentials(mutableCredentials).build(); - // 4 - include logic for when/how to update your mutableCredentials - // In this example we'll use a SchedulerExecutorService to periodically check for updates - ThreadFactory daemonThreadFactory = - runnable -> { - Thread thread = new Thread(runnable, "spanner-mutable-credentials-rotator"); - thread.setDaemon(true); - return thread; - }; - ScheduledExecutorService executorService = - Executors.newSingleThreadScheduledExecutor(daemonThreadFactory); - executorService.scheduleAtFixedRate( - () -> { - try { - FileTime currentModifiedTime = Files.getLastModifiedTime(path); - if (currentModifiedTime.compareTo(lastModifiedTime.get()) > 0) { - lastModifiedTime.set(currentModifiedTime); - try (FileInputStream is = new FileInputStream(credentialsPath)) { - ServiceAccountCredentials credentials = ServiceAccountCredentials.fromStream(is); - mutableCredentials.updateCredentials(credentials); - } - } - } catch (IOException e) { - System.err.println("Failed to check or update credentials: " + e.getMessage()); - } - }, - 15, - 15, - TimeUnit.MINUTES); + // 4 - include logic for when/how to update your mutableCredentials + // In this example we'll use a SchedulerExecutorService to periodically check for updates + ThreadFactory daemonThreadFactory = + runnable -> { + Thread thread = new Thread(runnable, "spanner-mutable-credentials-rotator"); + thread.setDaemon(true); + return thread; + }; + ScheduledExecutorService executorService = + Executors.newSingleThreadScheduledExecutor(daemonThreadFactory); + executorService.scheduleAtFixedRate( + () -> { + try { + FileTime currentModifiedTime = Files.getLastModifiedTime(path); + if (currentModifiedTime.compareTo(lastModifiedTime.get()) > 0) { + lastModifiedTime.set(currentModifiedTime); + try (FileInputStream is = new FileInputStream(credentialsPath)) { + ServiceAccountCredentials credentials = ServiceAccountCredentials.fromStream(is); + mutableCredentials.updateCredentials(credentials); + } + } + } catch (IOException e) { + System.err.println("Failed to check or update credentials: " + e.getMessage()); + } + }, + 15, + 15, + TimeUnit.MINUTES); - // 5. Use the client - try (Spanner spanner = options.getService(); - DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { - // Perform operations... - // long running client operations will always use the latest credentials wrapped in - // mutableCredentials - } finally { - // Ensure the executor is shut down when the application exits or the client is closed - executorService.shutdown(); - } + // 5. Use the client + try (Spanner spanner = options.getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + // Perform operations... + // long running client operations will always use the latest credentials wrapped in + // mutableCredentials + } finally { + // Ensure the executor is shut down when the application exits or the client is closed + executorService.shutdown(); } + } } // [END spanner_mutable_credentials]