From a313c4eac34d6a2c304b3efe22632cfdb05d3031 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Tue, 10 Mar 2026 09:18:23 -0400 Subject: [PATCH 1/6] doc: add MutableCredentials Example --- samples/install-without-bom/pom.xml | 2 +- .../spanner/MutableCredentialsExample.java | 98 +++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index a504dc626be..6334cf05ef9 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -33,7 +33,7 @@ com.google.cloud google-cloud-spanner - 6.110.0 + 6.111.1 diff --git a/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java new file mode 100644 index 00000000000..33b2ace4c24 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java @@ -0,0 +1,98 @@ +/* + * 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.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.cloud.spanner.connection.MutableCredentials; +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 array to hold the mutable lastModifiedTime so it can be accessed in the lambda + FileTime[] lastModifiedTime = new FileTime[] {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[0]) > 0) { + lastModifiedTime[0] = 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 04c03681fb62baa8d9c24e45351cb80d15adc931 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Tue, 10 Mar 2026 09:23:01 -0400 Subject: [PATCH 2/6] fix import --- .../java/com/example/spanner/MutableCredentialsExample.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java index 33b2ace4c24..057411127ed 100644 --- a/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java +++ b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java @@ -22,7 +22,7 @@ import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; -import com.google.cloud.spanner.connection.MutableCredentials; +import com.google.cloud.spanner.MutableCredentials; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; From 1ba2c74aeefbd56ce9ce2a459dee4c7ba81979f2 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Tue, 10 Mar 2026 13:27:40 +0000 Subject: [PATCH 3/6] chore: generate libraries at Tue Mar 10 13:24:49 UTC 2026 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dbcde8f8978..f65f5c0e34e 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ If you are using Maven without the BOM, add this to your dependencies: com.google.cloud google-cloud-spanner - 6.110.0 + 6.111.1 ``` @@ -381,6 +381,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-spanner/tree/ | List Databases Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/ListDatabasesSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/ListDatabasesSample.java) | | List Instance Config Operations Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigOperationsSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigOperationsSample.java) | | List Instance Configs Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigsSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigsSample.java) | +| Mutable Credentials Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java) | | Pg Alter Sequence Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgAlterSequenceSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgAlterSequenceSample.java) | | Pg Async Query To List Async Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgAsyncQueryToListAsyncExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgAsyncQueryToListAsyncExample.java) | | Pg Async Runner Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgAsyncRunnerExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgAsyncRunnerExample.java) | From 7db9a6c5cd3a2dfda61d131ed5793875345fbc4c Mon Sep 17 00:00:00 2001 From: ldetmer Date: Tue, 10 Mar 2026 09:32:37 -0400 Subject: [PATCH 4/6] fix lint issue --- .../java/com/example/spanner/MutableCredentialsExample.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java index 057411127ed..d0ff08eea6f 100644 --- a/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java +++ b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java @@ -19,10 +19,10 @@ // [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 com.google.cloud.spanner.MutableCredentials; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; From bf3aec5e2582bbdd9c1718bef0da06c38fae3572 Mon Sep 17 00:00:00 2001 From: ldetmer <1771267+ldetmer@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:01:19 -0400 Subject: [PATCH 5/6] Update to use AtomicReference Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../java/com/example/spanner/MutableCredentialsExample.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java index d0ff08eea6f..45f83e1d25f 100644 --- a/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java +++ b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java @@ -39,8 +39,9 @@ public class MutableCredentialsExample { static void createClientWithMutableCredentials() throws IOException { final String credentialsPath = "location_of_service_account_credential_json"; Path path = Paths.get(credentialsPath); - // Use an array to hold the mutable lastModifiedTime so it can be accessed in the lambda - FileTime[] lastModifiedTime = new FileTime[] {Files.getLastModifiedTime(path)}; + // 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; From 76a33acf98ade139fcbd000714dacafc94f8048d Mon Sep 17 00:00:00 2001 From: ldetmer <1771267+ldetmer@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:01:32 -0400 Subject: [PATCH 6/6] Update to use AtomicReference 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/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java index 45f83e1d25f..2b000608223 100644 --- a/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java +++ b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java @@ -69,8 +69,8 @@ static void createClientWithMutableCredentials() throws IOException { () -> { try { FileTime currentModifiedTime = Files.getLastModifiedTime(path); - if (currentModifiedTime.compareTo(lastModifiedTime[0]) > 0) { - lastModifiedTime[0] = currentModifiedTime; + if (currentModifiedTime.compareTo(lastModifiedTime.get()) > 0) { + lastModifiedTime.set(currentModifiedTime); try (FileInputStream is = new FileInputStream(credentialsPath)) { ServiceAccountCredentials credentials = ServiceAccountCredentials.fromStream(is); mutableCredentials.updateCredentials(credentials);