Repository: googleapis/java-storage
Pull request #2534 (2024-05-08 01:02:19)

test: add GrpcPlainRequestLoggingInterceptor and add it to BackendResources
Grpc's built-in logging is very robust, but does not print request/response messages in "plain text", instead logging byte codes sequences. This interceptor instead logs the protobuf messages in their normal plain text rendering. By default, the logging is disabled. The category needs to be set to CONFIG or higher to see the logs.





Patch:
diff --git a/.github/trusted-contribution.yml b/.github/trusted-contribution.yml
index a0ba1f7d..88d3ac9b 100644
--- a/.github/trusted-contribution.yml
+++ b/.github/trusted-contribution.yml
@@ -1,3 +1,9 @@
 trustedContributors:
 - renovate-bot
 - gcf-owl-bot[bot]
+
+annotations:
+- type: comment
+  text: "/gcbrun"
+- type: label
+  text: "kokoro:force-run"
diff --git a/.github/workflows/unmanaged_dependency_check.yaml b/.github/workflows/unmanaged_dependency_check.yaml
index b7ee2ca7..f15d6521 100644
--- a/.github/workflows/unmanaged_dependency_check.yaml
+++ b/.github/workflows/unmanaged_dependency_check.yaml
@@ -17,6 +17,6 @@ jobs:
           # repository
           .kokoro/build.sh
       - name: Unmanaged dependency check
-        uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.29.0
+        uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.30.0
         with:
           bom-path: google-cloud-storage-bom/pom.xml
diff --git a/.kokoro/presubmit/graalvm-native-17.cfg b/.kokoro/presubmit/graalvm-native-17.cfg
index 7c1a9bb6..8bcc468e 100644
--- a/.kokoro/presubmit/graalvm-native-17.cfg
+++ b/.kokoro/presubmit/graalvm-native-17.cfg
@@ -3,7 +3,7 @@
 # Configure the docker image for kokoro-trampoline.
 env_vars: {
   key: "TRAMPOLINE_IMAGE"
-  value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.29.0"
+  value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.30.0"
 }
 
 env_vars: {
diff --git a/.kokoro/presubmit/graalvm-native.cfg b/.kokoro/presubmit/graalvm-native.cfg
index 39055d62..65d8c499 100644
--- a/.kokoro/presubmit/graalvm-native.cfg
+++ b/.kokoro/presubmit/graalvm-native.cfg
@@ -3,7 +3,7 @@
 # Configure the docker image for kokoro-trampoline.
 env_vars: {
   key: "TRAMPOLINE_IMAGE"
-  value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.29.0"
+  value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.30.0"
 }
 
 env_vars: {
diff --git a/.readme-partials.yaml b/.readme-partials.yaml
index 5f1cc7ed..3858d893 100644
--- a/.readme-partials.yaml
+++ b/.readme-partials.yaml
@@ -32,19 +32,19 @@ custom_content: |
   <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-storage-control</artifactId>
-      <version>2.36.1-alpha</version><!-- {x-version-update:google-cloud-storage:released} -->
+      <version>2.38.1-beta-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage-control:current} -->
   </dependency>
   ```
   
   If you are using Gradle 5.x or later, add this to your dependencies:
   ```Groovy
-  implementation platform('com.google.cloud:libraries-bom:26.37.0')
+  implementation platform('com.google.cloud:libraries-bom:2.38.0')
   implementation 'com.google.cloud:google-cloud-storage-control'
   ```
   If you are using Gradle without BOM, add this to your dependencies:
   
   ```Groovy
-  implementation 'com.google.cloud:google-cloud-storage-control:2.36.1-alpha' <!-- {x-version-update:google-cloud-storage:released} -->
+  implementation 'com.google.cloud:google-cloud-storage-control:2.38.1-beta-SNAPSHOT' <!-- {x-version-update:google-cloud-storage-control:current} -->
   ```
   
   #### Creating an authorized service object
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 47ad7b8c..a0634b2f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,36 @@
 # Changelog
 
+## [2.38.0](https://github.com/googleapis/java-storage/compare/v2.37.0...v2.38.0) (2024-05-09)
+
+
+### Features
+
+* Promoted google-cloud-storage-control to beta ([#2531](https://github.com/googleapis/java-storage/issues/2531)) ([09f7191](https://github.com/googleapis/java-storage/commit/09f719194fd9296ecb21758096b8e65eb208dfc2))
+
+
+### Bug Fixes
+
+* Add strict client side response validation for gRPC chunked resumable uploads ([#2527](https://github.com/googleapis/java-storage/issues/2527)) ([c1d1f4a](https://github.com/googleapis/java-storage/commit/c1d1f4a5c88d27296f69df0a832659e2b1eb9ca0))
+* An existing resource pattern value `projects/{project}/buckets/{bucket}/managedFolders/{managedFolder=**}` to resource definition `storage.googleapis.com/ManagedFolder` is removed ([#2524](https://github.com/googleapis/java-storage/issues/2524)) ([7d7f526](https://github.com/googleapis/java-storage/commit/7d7f5263f7e7f5357c5970c68c4d6ff117fc9e93))
+* **deps:** Update the Java code generator (gapic-generator-java) to 2.39.0 ([#2501](https://github.com/googleapis/java-storage/issues/2501)) ([518d4be](https://github.com/googleapis/java-storage/commit/518d4beefdc3738fd19168baccd4016dd002d4fa))
+* ParallelCompositeUpload in Transfer Manager hangs when encountering OOM ([#2526](https://github.com/googleapis/java-storage/issues/2526)) ([67a7c6b](https://github.com/googleapis/java-storage/commit/67a7c6b6582b5afab9272b32e01897d009e7c0bf))
+* Update grpc WriteObject response handling to provide context when a failure happens ([#2532](https://github.com/googleapis/java-storage/issues/2532)) ([170a3f5](https://github.com/googleapis/java-storage/commit/170a3f54c8ccb6af108c35992e652c07ebb1109c))
+* Update GzipReadableByteChannel to be tolerant of one byte reads ([#2512](https://github.com/googleapis/java-storage/issues/2512)) ([87b63f4](https://github.com/googleapis/java-storage/commit/87b63f4995f7b9654f40a9585996ec4a73235858))
+* Update StorageOptions to carry forward fields that aren't part of ServiceOptions ([#2521](https://github.com/googleapis/java-storage/issues/2521)) ([b84654e](https://github.com/googleapis/java-storage/commit/b84654e79ee53087badc0315ea3fb2d1b0b9ca57))
+
+
+### Dependencies
+
+* Update dependency com.google.cloud:sdk-platform-java-config to v3.30.0 ([#2523](https://github.com/googleapis/java-storage/issues/2523)) ([3e573f7](https://github.com/googleapis/java-storage/commit/3e573f79b3935e423e200537549507ab1f252ada))
+* Update dependency info.picocli:picocli to v4.7.6 ([#2535](https://github.com/googleapis/java-storage/issues/2535)) ([f26888a](https://github.com/googleapis/java-storage/commit/f26888ab1467d6bbcad9bfced9360daa4abd2824))
+
+
+### Documentation
+
+* Add in Transfer Manager chunked upload/download samples ([#2518](https://github.com/googleapis/java-storage/issues/2518)) ([d1f6bcc](https://github.com/googleapis/java-storage/commit/d1f6bccc2c9b5da5c4ff196711c5cd410649d60f))
+* Update readme to include gradle instructions for storage control ([#2503](https://github.com/googleapis/java-storage/issues/2503)) ([50ac93b](https://github.com/googleapis/java-storage/commit/50ac93b6b61806911737e389253739436dfb515c))
+* Update TransportCompatibility annotation for Storage#blobWriteSession ([#2520](https://github.com/googleapis/java-storage/issues/2520)) ([b7d673c](https://github.com/googleapis/java-storage/commit/b7d673cbac52407d470eace9d08e1cea2584da30))
+
 ## [2.37.0](https://github.com/googleapis/java-storage/compare/v2.36.1...v2.37.0) (2024-04-19)
 
 
diff --git a/README.md b/README.md
index 7355c427..fbd25481 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@ If you are using Maven without the BOM, add this to your dependencies:
 <dependency>
   <groupId>com.google.cloud</groupId>
   <artifactId>google-cloud-storage-control</artifactId>
-  <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+  <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
 </dependency>
 
 ```
@@ -59,20 +59,20 @@ If you are using Maven without the BOM, add this to your dependencies:
 If you are using Gradle 5.x or later, add this to your dependencies:
 
 ```Groovy
-implementation platform('com.google.cloud:libraries-bom:26.38.0')
+implementation platform('com.google.cloud:libraries-bom:26.39.0')
 
 implementation 'com.google.cloud:google-cloud-storage'
 ```
 If you are using Gradle without BOM, add this to your dependencies:
 
 ```Groovy
-implementation 'com.google.cloud:google-cloud-storage:2.37.0'
+implementation 'com.google.cloud:google-cloud-storage:2.38.0'
 ```
 
 If you are using SBT, add this to your dependencies:
 
 ```Scala
-libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "2.37.0"
+libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "2.38.0"
 ```
 <!-- {x-version-update-end} -->
 
@@ -141,19 +141,19 @@ If you are using Maven without the BOM, add this to your dependencies:
 <dependency>
     <groupId>com.google.cloud</groupId>
     <artifactId>google-cloud-storage-control</artifactId>
-    <version>2.36.1-alpha</version><!-- {x-version-update:google-cloud-storage:released} -->
+    <version>2.38.1-beta-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage-control:current} -->
 </dependency>
 ```
 
 If you are using Gradle 5.x or later, add this to your dependencies:
 ```Groovy
-implementation platform('com.google.cloud:libraries-bom:26.37.0')
+implementation platform('com.google.cloud:libraries-bom:2.38.0')
 implementation 'com.google.cloud:google-cloud-storage-control'
 ```
 If you are using Gradle without BOM, add this to your dependencies:
 
 ```Groovy
-implementation 'com.google.cloud:google-cloud-storage-control:2.36.1-alpha' <!-- {x-version-update:google-cloud-storage:released} -->
+implementation 'com.google.cloud:google-cloud-storage-control:2.38.1-beta-SNAPSHOT' <!-- {x-version-update:google-cloud-storage-control:current} -->
 ```
 
 #### Creating an authorized service object
@@ -494,7 +494,7 @@ Java is a registered trademark of Oracle and/or its affiliates.
 [kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-storage/java11.html
 [stability-image]: https://img.shields.io/badge/stability-stable-green
 [maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-storage.svg
-[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-storage/2.37.0
+[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-storage/2.38.0
 [authentication]: https://github.com/googleapis/google-cloud-java#authentication
 [auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes
 [predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles
diff --git a/gapic-google-cloud-storage-v2/pom.xml b/gapic-google-cloud-storage-v2/pom.xml
index 9370f812..605cdd06 100644
--- a/gapic-google-cloud-storage-v2/pom.xml
+++ b/gapic-google-cloud-storage-v2/pom.xml
@@ -4,13 +4,13 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.api.grpc</groupId>
   <artifactId>gapic-google-cloud-storage-v2</artifactId>
-  <version>2.37.1-alpha-SNAPSHOT</version><!-- {x-version-update:gapic-google-cloud-storage-v2:current} -->
+  <version>2.38.1-alpha-SNAPSHOT</version><!-- {x-version-update:gapic-google-cloud-storage-v2:current} -->
   <name>gapic-google-cloud-storage-v2</name>
   <description>GRPC library for gapic-google-cloud-storage-v2</description>
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>google-cloud-storage-parent</artifactId>
-    <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+    <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
   </parent>
   <dependencies>
     <dependency>
diff --git a/google-cloud-storage-bom/pom.xml b/google-cloud-storage-bom/pom.xml
index 69282077..c2e8f527 100644
--- a/google-cloud-storage-bom/pom.xml
+++ b/google-cloud-storage-bom/pom.xml
@@ -19,12 +19,12 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.cloud</groupId>
   <artifactId>google-cloud-storage-bom</artifactId>
-  <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+  <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
   <packaging>pom</packaging>
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>sdk-platform-java-config</artifactId>
-    <version>3.29.0</version>
+    <version>3.30.0</version>
     <relativePath/>
   </parent>
 
@@ -69,37 +69,37 @@
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>google-cloud-storage</artifactId>
-        <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+        <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
       </dependency>
       <dependency>
         <groupId>com.google.api.grpc</groupId>
         <artifactId>gapic-google-cloud-storage-v2</artifactId>
-        <version>2.37.1-alpha-SNAPSHOT</version><!-- {x-version-update:gapic-google-cloud-storage-v2:current} -->
+        <version>2.38.1-alpha-SNAPSHOT</version><!-- {x-version-update:gapic-google-cloud-storage-v2:current} -->
       </dependency>
       <dependency>
         <groupId>com.google.api.grpc</groupId>
         <artifactId>grpc-google-cloud-storage-v2</artifactId>
-        <version>2.37.1-alpha-SNAPSHOT</version><!-- {x-version-update:grpc-google-cloud-storage-v2:current} -->
+        <version>2.38.1-alpha-SNAPSHOT</version><!-- {x-version-update:grpc-google-cloud-storage-v2:current} -->
       </dependency>
       <dependency>
         <groupId>com.google.api.grpc</groupId>
         <artifactId>proto-google-cloud-storage-v2</artifactId>
-        <version>2.37.1-alpha-SNAPSHOT</version><!-- {x-version-update:proto-google-cloud-storage-v2:current} -->
+        <version>2.38.1-alpha-SNAPSHOT</version><!-- {x-version-update:proto-google-cloud-storage-v2:current} -->
       </dependency>
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>google-cloud-storage-control</artifactId>
-        <version>2.37.1-beta-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage-control:current} -->
+        <version>2.38.1-beta-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage-control:current} -->
       </dependency>
       <dependency>
         <groupId>com.google.api.grpc</groupId>
         <artifactId>grpc-google-cloud-storage-control-v2</artifactId>
-        <version>2.37.1-beta-SNAPSHOT</version><!-- {x-version-update:grpc-google-cloud-storage-control-v2:current} -->
+        <version>2.38.1-beta-SNAPSHOT</version><!-- {x-version-update:grpc-google-cloud-storage-control-v2:current} -->
       </dependency>
       <dependency>
         <groupId>com.google.api.grpc</groupId>
         <artifactId>proto-google-cloud-storage-control-v2</artifactId>
-        <version>2.37.1-beta-SNAPSHOT</version><!-- {x-version-update:proto-google-cloud-storage-control-v2:current} -->
+        <version>2.38.1-beta-SNAPSHOT</version><!-- {x-version-update:proto-google-cloud-storage-control-v2:current} -->
       </dependency>
     </dependencies>
   </dependencyManagement>
diff --git a/google-cloud-storage-control/pom.xml b/google-cloud-storage-control/pom.xml
index 314f91b9..c131e66b 100644
--- a/google-cloud-storage-control/pom.xml
+++ b/google-cloud-storage-control/pom.xml
@@ -5,13 +5,13 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.google.cloud</groupId>
     <artifactId>google-cloud-storage-control</artifactId>
-    <version>2.37.1-beta-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage-control:current} -->
+    <version>2.38.1-beta-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage-control:current} -->
     <name>google-cloud-storage-control</name>
     <description>GRPC library for google-cloud-storage-control</description>
     <parent>
         <groupId>com.google.cloud</groupId>
         <artifactId>google-cloud-storage-parent</artifactId>
-        <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+        <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
     </parent>
 
     <dependencies>
diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml
index 4528c5b8..830b4f18 100644
--- a/google-cloud-storage/pom.xml
+++ b/google-cloud-storage/pom.xml
@@ -2,7 +2,7 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <artifactId>google-cloud-storage</artifactId>
-  <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+  <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
   <packaging>jar</packaging>
   <name>Google Cloud Storage</name>
   <url>https://github.com/googleapis/java-storage</url>
@@ -12,11 +12,11 @@
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>google-cloud-storage-parent</artifactId>
-    <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+    <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
   </parent>
   <properties>
     <site.installationModule>google-cloud-storage</site.installationModule>
-    <pubsub-proto.version>1.111.2</pubsub-proto.version>
+    <pubsub-proto.version>1.111.3</pubsub-proto.version>
     <junit-platform.version>5.10.2</junit-platform.version>
   </properties>
   <dependencies>
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/AsyncStorageTaskException.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/AsyncStorageTaskException.java
new file mode 100644
index 00000000..7d7b585d
--- /dev/null
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/AsyncStorageTaskException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 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.google.cloud.storage;
+
+/**
+ * This exception is used to preserve the caller's stacktrace when invoking an async task in a sync
+ * context. It will be added as a suppressed exception when propagating the async exception. This
+ * allows callers to catch ApiException thrown in an async operation, while still maintaining the
+ * call site.
+ */
+public final class AsyncStorageTaskException extends RuntimeException {
+  // mimic of com.google.api.gax.rpc.AsyncTaskException which doesn't have a public constructor
+  // if that class is ever made public, make this class extend it
+  AsyncStorageTaskException() {
+    super("Asynchronous task failed");
+  }
+}
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedChunkedResumableWritableByteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedChunkedResumableWritableByteChannel.java
index bbcd986a..c3c3cd09 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedChunkedResumableWritableByteChannel.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedChunkedResumableWritableByteChannel.java
@@ -21,6 +21,7 @@ import static com.google.cloud.storage.GrpcUtils.contextWithBucketName;
 import com.google.api.core.SettableApiFuture;
 import com.google.api.gax.grpc.GrpcCallContext;
 import com.google.api.gax.retrying.ResultRetryAlgorithm;
+import com.google.api.gax.rpc.ApiException;
 import com.google.api.gax.rpc.ApiStreamObserver;
 import com.google.api.gax.rpc.ClientStreamingCallable;
 import com.google.api.gax.rpc.OutOfRangeException;
@@ -208,14 +209,19 @@ final class GapicUnbufferedChunkedResumableWritableByteChannel
         deps,
         alg,
         () -> {
-          Observer observer = new Observer(content, finalizing);
+          Observer observer = new Observer(content, finalizing, segments, internalContext);
           ApiStreamObserver<WriteObjectRequest> write = callable.clientStreamingCall(observer);
 
           for (WriteObjectRequest message : segments) {
             write.onNext(message);
           }
           write.onCompleted();
-          observer.await();
+          try {
+            observer.await();
+          } catch (Throwable t) {
+            t.addSuppressed(new AsyncStorageTaskException());
+            throw t;
+          }
           return null;
         },
         Decoder.identity());
@@ -230,13 +236,21 @@ final class GapicUnbufferedChunkedResumableWritableByteChannel
 
     private final RewindableContent content;
     private final boolean finalizing;
+    private final List<WriteObjectRequest> segments;
+    private final GrpcCallContext context;
 
     private final SettableApiFuture<Void> invocationHandle;
     private volatile WriteObjectResponse last;
 
-    Observer(@Nullable RewindableContent content, boolean finalizing) {
+    Observer(
+        @Nullable RewindableContent content,
+        boolean finalizing,
+        @NonNull List<WriteObjectRequest> segments,
+        GrpcCallContext context) {
       this.content = content;
       this.finalizing = finalizing;
+      this.segments = segments;
+      this.context = context;
       this.invocationHandle = SettableApiFuture.create();
     }
 
@@ -250,10 +264,20 @@ final class GapicUnbufferedChunkedResumableWritableByteChannel
       if (t instanceof OutOfRangeException) {
         OutOfRangeException oore = (OutOfRangeException) t;
         open = false;
-        invocationHandle.setException(
-            ResumableSessionFailureScenario.SCENARIO_5.toStorageException());
-      } else {
-        invocationHandle.setException(t);
+        StorageException storageException =
+            ResumableSessionFailureScenario.SCENARIO_5.toStorageException(
+                segments, null, context, oore);
+        invocationHandle.setException(storageException);
+      } else if (t instanceof ApiException) {
+        // use StorageExceptions logic to translate from ApiException to our status codes ensuring
+        // things fall in line with our retry handlers.
+        // This is suboptimal, as it will initialize a second exception, however this is the
+        // unusual case, and it should not cause a significant overhead given its rarity.
+        StorageException tmp = StorageException.asStorageException((ApiException) t);
+        StorageException storageException =
+            ResumableSessionFailureScenario.toStorageException(
+                tmp.getCode(), tmp.getMessage(), tmp.getReason(), segments, null, context, t);
+        invocationHandle.setException(storageException);
       }
     }
 
@@ -276,7 +300,8 @@ final class GapicUnbufferedChunkedResumableWritableByteChannel
             writeCtx.getTotalSentBytes().set(persistedSize);
             writeCtx.getConfirmedBytes().set(persistedSize);
           } else {
-            throw ResumableSessionFailureScenario.SCENARIO_7.toStorageException();
+            throw ResumableSessionFailureScenario.SCENARIO_7.toStorageException(
+                segments, last, context, null);
           }
         } else if (finalizing && last.hasResource()) {
           long totalSentBytes = writeCtx.getTotalSentBytes().get();
@@ -285,22 +310,28 @@ final class GapicUnbufferedChunkedResumableWritableByteChannel
             writeCtx.getConfirmedBytes().set(finalSize);
             resultFuture.set(last);
           } else if (finalSize < totalSentBytes) {
-            throw ResumableSessionFailureScenario.SCENARIO_4_1.toStorageException();
+            throw ResumableSessionFailureScenario.SCENARIO_4_1.toStorageException(
+                segments, last, context, null);
           } else {
-            throw ResumableSessionFailureScenario.SCENARIO_4_2.toStorageException();
+            throw ResumableSessionFailureScenario.SCENARIO_4_2.toStorageException(
+                segments, last, context, null);
           }
         } else if (!finalizing && last.hasResource()) {
-          throw ResumableSessionFailureScenario.SCENARIO_1.toStorageException();
+          throw ResumableSessionFailureScenario.SCENARIO_1.toStorageException(
+              segments, last, context, null);
         } else if (finalizing && last.hasPersistedSize()) {
           long totalSentBytes = writeCtx.getTotalSentBytes().get();
           long persistedSize = last.getPersistedSize();
           if (persistedSize < totalSentBytes) {
-            throw ResumableSessionFailureScenario.SCENARIO_3.toStorageException();
+            throw ResumableSessionFailureScenario.SCENARIO_3.toStorageException(
+                segments, last, context, null);
           } else {
-            throw ResumableSessionFailureScenario.SCENARIO_2.toStorageException();
+            throw ResumableSessionFailureScenario.SCENARIO_2.toStorageException(
+                segments, last, context, null);
           }
         } else {
-          throw ResumableSessionFailureScenario.SCENARIO_0.toStorageException();
+          throw ResumableSessionFailureScenario.SCENARIO_0.toStorageException(
+              segments, last, context, null);
         }
       } catch (Throwable se) {
         open = false;
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSessionFailureScenario.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSessionFailureScenario.java
index 0bccb2ff..294d481c 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSessionFailureScenario.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSessionFailureScenario.java
@@ -16,19 +16,31 @@
 
 package com.google.cloud.storage;
 
+import static com.google.cloud.storage.Utils.ifNonNull;
+
 import com.google.api.client.http.HttpHeaders;
 import com.google.api.client.http.HttpResponse;
 import com.google.api.client.http.HttpResponseException;
+import com.google.api.gax.grpc.GrpcCallContext;
+import com.google.api.gax.rpc.ApiException;
 import com.google.cloud.BaseServiceException;
 import com.google.cloud.storage.StorageException.IOExceptionCallable;
 import com.google.common.io.CharStreams;
+import com.google.protobuf.MessageOrBuilder;
+import com.google.storage.v2.ChecksummedData;
+import com.google.storage.v2.ObjectChecksums;
+import com.google.storage.v2.WriteObjectRequest;
+import com.google.storage.v2.WriteObjectResponse;
+import io.grpc.StatusRuntimeException;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import javax.annotation.ParametersAreNonnullByDefault;
+import org.checkerframework.checker.nullness.qual.NonNull;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 @ParametersAreNonnullByDefault
@@ -69,6 +81,10 @@ enum ResumableSessionFailureScenario {
   private static final String PREFIX_I = "\t|< ";
   private static final String PREFIX_O = "\t|> ";
   private static final String PREFIX_X = "\t|  ";
+  // define some constants for tab widths that are more compressed that the literals
+  private static final String T1 = "\t";
+  private static final String T2 = "\t\t";
+  private static final String T3 = "\t\t\t";
 
   private static final Predicate<String> includedHeaders =
       matches("Content-Length")
@@ -78,6 +94,7 @@ enum ResumableSessionFailureScenario {
           .or(matches("Range"))
           .or(startsWith("X-Goog-Stored-"))
           .or(matches("X-Goog-GCS-Idempotency-Token"))
+          .or(matches("X-Goog-request-params"))
           .or(matches("X-GUploader-UploadID"));
 
   private static final Predicate<Map.Entry<String, ?>> includeHeader =
@@ -116,8 +133,12 @@ enum ResumableSessionFailureScenario {
     return toStorageException(code, message, reason, uploadId, resp, cause, contentCallable);
   }
 
-  StorageException toStorageException() {
-    return new StorageException(code, message, reason, null);
+  StorageException toStorageException(
+      @NonNull List<@NonNull WriteObjectRequest> reqs,
+      @Nullable WriteObjectResponse resp,
+      @NonNull GrpcCallContext context,
+      @Nullable Throwable cause) {
+    return toStorageException(code, message, reason, reqs, resp, context, cause);
   }
 
   static StorageException toStorageException(
@@ -136,6 +157,102 @@ enum ResumableSessionFailureScenario {
     return se;
   }
 
+  static StorageException toStorageException(
+      int code,
+      String message,
+      @Nullable String reason,
+      @NonNull List<@NonNull WriteObjectRequest> reqs,
+      @Nullable WriteObjectResponse resp,
+      @NonNull GrpcCallContext context,
+      @Nullable Throwable cause) {
+    final StringBuilder sb = new StringBuilder();
+    sb.append(message);
+    // request context
+    Map<String, List<String>> extraHeaders = context.getExtraHeaders();
+    recordHeadersTo(extraHeaders, PREFIX_O, sb);
+    int length = reqs.size();
+    for (int i = 0; i < length; i++) {
+      if (i == 0) {
+        sb.append("\n").append(PREFIX_O).append("[");
+      } else {
+        sb.append(",");
+      }
+      WriteObjectRequest req = reqs.get(i);
+      sb.append("\n").append(PREFIX_O).append(T1).append(req.getClass().getName()).append("{");
+      if (req.hasUploadId()) {
+        sb.append("\n").append(PREFIX_O).append(T2).append("upload_id: ").append(req.getUploadId());
+      }
+      long writeOffset = req.getWriteOffset();
+      if (req.hasChecksummedData()) {
+        ChecksummedData checksummedData = req.getChecksummedData();
+        sb.append("\n").append(PREFIX_O).append(T2);
+        sb.append(
+            String.format(
+                "checksummed_data: {range: [%d:%d]",
+                writeOffset, writeOffset + checksummedData.getContent().size()));
+        if (checksummedData.hasCrc32C()) {
+          sb.append(", crc32c: ").append(checksummedData.getCrc32C());
+        }
+        sb.append("}");
+      } else {
+        sb.append("\n").append(PREFIX_O).append(T2).append("write_offset: ").append(writeOffset);
+      }
+      if (req.getFinishWrite()) {
+        sb.append("\n").append(PREFIX_O).append(T2).append("finish_write: true");
+      }
+      if (req.hasObjectChecksums()) {
+        ObjectChecksums objectChecksums = req.getObjectChecksums();
+        sb.append("\n").append(PREFIX_O).append(T2).append("object_checksums: ").append("{");
+        fmt(objectChecksums, PREFIX_O, T3, sb);
+        sb.append("\n").append(PREFIX_O).append(T2).append("}");
+      }
+      sb.append("\n").append(PREFIX_O).append("\t}");
+      if (i == length - 1) {
+        sb.append("\n").append(PREFIX_O).append("]");
+      }
+    }
+
+    sb.append("\n").append(PREFIX_X);
+
+    // response context
+    if (resp != null) {
+      sb.append("\n").append(PREFIX_I).append(resp.getClass().getName()).append("{");
+      fmt(resp, PREFIX_I, T1, sb);
+      sb.append("\n").append(PREFIX_I).append("}");
+      sb.append("\n").append(PREFIX_X);
+    }
+
+    if (cause != null) {
+      if (cause instanceof ApiException) {
+        ApiException apiException = (ApiException) cause;
+        Throwable cause1 = apiException.getCause();
+        if (cause1 instanceof StatusRuntimeException) {
+          StatusRuntimeException statusRuntimeException = (StatusRuntimeException) cause1;
+          sb.append("\n").append(PREFIX_I).append(statusRuntimeException.getStatus());
+          ifNonNull(
+              statusRuntimeException.getTrailers(),
+              t -> sb.append("\n").append(PREFIX_I).append(t));
+        } else {
+          sb.append("\n")
+              .append(PREFIX_I)
+              .append("code: ")
+              .append(apiException.getStatusCode().toString());
+          ifNonNull(
+              apiException.getReason(),
+              r -> sb.append("\n").append(PREFIX_I).append("reason: ").append(r));
+          ifNonNull(
+              apiException.getDomain(),
+              d -> sb.append("\n").append(PREFIX_I).append("domain: ").append(d));
+          ifNonNull(
+              apiException.getErrorDetails(),
+              e -> sb.append("\n").append(PREFIX_I).append("errorDetails: ").append(e));
+        }
+        sb.append("\n").append(PREFIX_X);
+      }
+    }
+    return new StorageException(code, sb.toString(), reason, cause);
+  }
+
   static StorageException toStorageException(
       int overrideCode,
       String message,
@@ -213,14 +330,21 @@ enum ResumableSessionFailureScenario {
   }
 
   private static void recordHeaderTo(HttpHeaders h, String prefix, StringBuilder sb) {
-    h.entrySet().stream()
-        .filter(includeHeader)
-        .forEach(
-            e -> {
-              String key = e.getKey();
-              String value = headerValueToString(e.getValue());
-              sb.append("\n").append(prefix).append(key).append(": ").append(value);
-            });
+    h.entrySet().stream().filter(includeHeader).forEach(writeHeaderValue(prefix, sb));
+  }
+
+  private static void recordHeadersTo(
+      Map<String, List<String>> headers, String prefix, StringBuilder sb) {
+    headers.entrySet().stream().filter(includeHeader).forEach(writeHeaderValue(prefix, sb));
+  }
+
+  private static <V> Consumer<Map.Entry<String, V>> writeHeaderValue(
+      String prefix, StringBuilder sb) {
+    return e -> {
+      String key = e.getKey();
+      String value = headerValueToString(e.getValue());
+      sb.append("\n").append(prefix).append(key).append(": ").append(value);
+    };
   }
 
   private static String headerValueToString(Object o) {
@@ -233,4 +357,18 @@ enum ResumableSessionFailureScenario {
 
     return o.toString();
   }
+
+  private static void fmt(
+      MessageOrBuilder msg,
+      @SuppressWarnings("SameParameterValue") String prefix,
+      String indentation,
+      StringBuilder sb) {
+    String string = msg.toString();
+    // drop the final new line before prefixing
+    string = string.replaceAll("\n$", "");
+    sb.append("\n")
+        .append(prefix)
+        .append(indentation)
+        .append(string.replaceAll("\r?\n", "\n" + prefix + indentation));
+  }
 }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java
index eeb24a44..63708115 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java
@@ -139,13 +139,19 @@ public final class StorageException extends BaseHttpServiceException {
     }
     // If there is a gRPC exception in our cause change pull it's error message up to be our
     // message otherwise, create a generic error message with the status code.
-    String statusCodeName = statusCode.getCode().name();
-    String statusExceptionMessage = getStatusExceptionMessage(apiEx);
-
-    String message;
-    if (statusExceptionMessage != null) {
-      message = statusCodeName + ": " + statusExceptionMessage;
-    } else {
+    String message = null;
+    if (apiEx.getCause() != null) {
+      Throwable cause = apiEx.getCause();
+      if (cause instanceof StatusRuntimeException || cause instanceof StatusException) {
+        message = cause.getMessage();
+      }
+      // if not a grpc exception fall through to the default handling
+    }
+    if (message == null && apiEx.getMessage() != null) {
+      message = apiEx.getMessage();
+    }
+    if (message == null) {
+      String statusCodeName = statusCode.getCode().name();
       message = "Error: " + statusCodeName;
     }
 
@@ -198,19 +204,6 @@ public final class StorageException extends BaseHttpServiceException {
     }
   }
 
-  @Nullable
-  private static String getStatusExceptionMessage(Exception apiEx) {
-    if (apiEx.getMessage() != null) {
-      return apiEx.getMessage();
-    } else {
-      Throwable cause = apiEx.getCause();
-      if (cause instanceof StatusRuntimeException || cause instanceof StatusException) {
-        return cause.getMessage();
-      }
-      return null;
-    }
-  }
-
   @Nullable
   private static ApiException asApiExceptionOrNull(Throwable cause) {
     if (cause instanceof ApiException) {
diff --git a/grpc-google-cloud-storage-control-v2/pom.xml b/grpc-google-cloud-storage-control-v2/pom.xml
index 87b56195..f7abf20d 100644
--- a/grpc-google-cloud-storage-control-v2/pom.xml
+++ b/grpc-google-cloud-storage-control-v2/pom.xml
@@ -4,13 +4,13 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.api.grpc</groupId>
   <artifactId>grpc-google-cloud-storage-control-v2</artifactId>
-  <version>2.37.1-beta-SNAPSHOT</version><!-- {x-version-update:grpc-google-cloud-storage-control-v2:current} -->
+  <version>2.38.1-beta-SNAPSHOT</version><!-- {x-version-update:grpc-google-cloud-storage-control-v2:current} -->
   <name>grpc-google-cloud-storage-control-v2</name>
   <description>GRPC library for google-cloud-storage</description>
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>google-cloud-storage-parent</artifactId>
-    <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+    <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
   </parent>
   <dependencies>
     <dependency>
diff --git a/grpc-google-cloud-storage-v2/pom.xml b/grpc-google-cloud-storage-v2/pom.xml
index 6f7a88ec..509018e2 100644
--- a/grpc-google-cloud-storage-v2/pom.xml
+++ b/grpc-google-cloud-storage-v2/pom.xml
@@ -4,13 +4,13 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.api.grpc</groupId>
   <artifactId>grpc-google-cloud-storage-v2</artifactId>
-  <version>2.37.1-alpha-SNAPSHOT</version><!-- {x-version-update:grpc-google-cloud-storage-v2:current} -->
+  <version>2.38.1-alpha-SNAPSHOT</version><!-- {x-version-update:grpc-google-cloud-storage-v2:current} -->
   <name>grpc-google-cloud-storage-v2</name>
   <description>GRPC library for grpc-google-cloud-storage-v2</description>
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>google-cloud-storage-parent</artifactId>
-    <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+    <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
   </parent>
   <dependencies>
     <dependency>
diff --git a/owlbot.py b/owlbot.py
index c6f9044a..b88da7d2 100644
--- a/owlbot.py
+++ b/owlbot.py
@@ -37,6 +37,7 @@ java.common_templates(excludes=[
   '.kokoro/presubmit/graalvm-native-17.cfg',
   '.kokoro/requirements.in',
   '.kokoro/requirements.txt',
+  '.github/trusted-contribution.yml',
   '.github/workflows/auto-release.yaml',
   'CONTRIBUTING.md',
   'renovate.json'
diff --git a/pom.xml b/pom.xml
index 024837fe..86da4c26 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
   <groupId>com.google.cloud</groupId>
   <artifactId>google-cloud-storage-parent</artifactId>
   <packaging>pom</packaging>
-  <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+  <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
   <name>Storage Parent</name>
   <url>https://github.com/googleapis/java-storage</url>
   <description>
@@ -14,7 +14,7 @@
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>sdk-platform-java-config</artifactId>
-    <version>3.29.0</version>
+    <version>3.30.0</version>
   </parent>
 
   <developers>
@@ -76,7 +76,7 @@
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>google-cloud-storage</artifactId>
-        <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+        <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
       </dependency>
       <dependency>
         <groupId>com.google.apis</groupId>
@@ -86,7 +86,7 @@
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>google-cloud-pubsub</artifactId>
-        <version>1.129.2</version>
+        <version>1.129.3</version>
         <scope>test</scope>
       </dependency>
       <dependency>
@@ -98,32 +98,32 @@
       <dependency>
         <groupId>com.google.api.grpc</groupId>
         <artifactId>proto-google-cloud-storage-v2</artifactId>
-        <version>2.37.1-alpha-SNAPSHOT</version><!-- {x-version-update:proto-google-cloud-storage-v2:current} -->
+        <version>2.38.1-alpha-SNAPSHOT</version><!-- {x-version-update:proto-google-cloud-storage-v2:current} -->
       </dependency>
       <dependency>
         <groupId>com.google.api.grpc</groupId>
         <artifactId>grpc-google-cloud-storage-v2</artifactId>
-        <version>2.37.1-alpha-SNAPSHOT</version><!-- {x-version-update:grpc-google-cloud-storage-v2:current} -->
+        <version>2.38.1-alpha-SNAPSHOT</version><!-- {x-version-update:grpc-google-cloud-storage-v2:current} -->
       </dependency>
       <dependency>
         <groupId>com.google.api.grpc</groupId>
         <artifactId>gapic-google-cloud-storage-v2</artifactId>
-        <version>2.37.1-alpha-SNAPSHOT</version><!-- {x-version-update:grpc-google-cloud-storage-v2:current} -->
+        <version>2.38.1-alpha-SNAPSHOT</version><!-- {x-version-update:grpc-google-cloud-storage-v2:current} -->
       </dependency>
       <dependency>
         <groupId>com.google.api.grpc</groupId>
         <artifactId>grpc-google-cloud-storage-control-v2</artifactId>
-        <version>2.37.1-beta-SNAPSHOT</version><!-- {x-version-update:grpc-google-cloud-storage-control-v2:current} -->
+        <version>2.38.1-beta-SNAPSHOT</version><!-- {x-version-update:grpc-google-cloud-storage-control-v2:current} -->
       </dependency>
       <dependency>
         <groupId>com.google.api.grpc</groupId>
         <artifactId>proto-google-cloud-storage-control-v2</artifactId>
-        <version>2.37.1-beta-SNAPSHOT</version><!-- {x-version-update:proto-google-cloud-storage-control-v2:current} -->
+        <version>2.38.1-beta-SNAPSHOT</version><!-- {x-version-update:proto-google-cloud-storage-control-v2:current} -->
       </dependency>
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>google-cloud-storage-control</artifactId>
-        <version>2.37.1-beta-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage-control:current} -->
+        <version>2.38.1-beta-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage-control:current} -->
       </dependency>
       <dependency>
         <groupId>com.google.cloud</groupId>
diff --git a/proto-google-cloud-storage-control-v2/pom.xml b/proto-google-cloud-storage-control-v2/pom.xml
index 9e57bf1a..c20dc636 100644
--- a/proto-google-cloud-storage-control-v2/pom.xml
+++ b/proto-google-cloud-storage-control-v2/pom.xml
@@ -4,13 +4,13 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.api.grpc</groupId>
   <artifactId>proto-google-cloud-storage-control-v2</artifactId>
-  <version>2.37.1-beta-SNAPSHOT</version><!-- {x-version-update:proto-google-cloud-storage-control-v2:current} -->
+  <version>2.38.1-beta-SNAPSHOT</version><!-- {x-version-update:proto-google-cloud-storage-control-v2:current} -->
   <name>proto-google-cloud-storage-control-v2</name>
   <description>Proto library for  proto-google-cloud-storage-control-v2</description>
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>google-cloud-storage-parent</artifactId>
-    <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+    <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
   </parent>
   <dependencies>
     <dependency>
diff --git a/proto-google-cloud-storage-v2/pom.xml b/proto-google-cloud-storage-v2/pom.xml
index b7d384dd..9b0942c2 100644
--- a/proto-google-cloud-storage-v2/pom.xml
+++ b/proto-google-cloud-storage-v2/pom.xml
@@ -4,13 +4,13 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.api.grpc</groupId>
   <artifactId>proto-google-cloud-storage-v2</artifactId>
-  <version>2.37.1-alpha-SNAPSHOT</version><!-- {x-version-update:proto-google-cloud-storage-v2:current} -->
+  <version>2.38.1-alpha-SNAPSHOT</version><!-- {x-version-update:proto-google-cloud-storage-v2:current} -->
   <name>proto-google-cloud-storage-v2</name>
   <description>PROTO library for proto-google-cloud-storage-v2</description>
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>google-cloud-storage-parent</artifactId>
-    <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+    <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
   </parent>
   <dependencies>
     <dependency>
diff --git a/proto-google-cloud-storage-v2/src/main/proto/google/storage/v2/storage.proto b/proto-google-cloud-storage-v2/src/main/proto/google/storage/v2/storage.proto
index 62922ead..0f9758f1 100644
--- a/proto-google-cloud-storage-v2/src/main/proto/google/storage/v2/storage.proto
+++ b/proto-google-cloud-storage-v2/src/main/proto/google/storage/v2/storage.proto
@@ -1,4 +1,4 @@
-// Copyright 2023 Google LLC
+// Copyright 2024 Google LLC
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml
index 610ecce6..fe5516e8 100644
--- a/samples/install-without-bom/pom.xml
+++ b/samples/install-without-bom/pom.xml
@@ -35,7 +35,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-storage-control</artifactId>
-      <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+      <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
     </dependency>
     <!-- [END storage_install_without_bom] -->
 
@@ -66,7 +66,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-pubsub</artifactId>
-      <version>1.129.2</version>
+      <version>1.129.3</version>
       <scope>test</scope>
     </dependency>
     <dependency>
diff --git a/samples/native-image-sample/pom.xml b/samples/native-image-sample/pom.xml
index 59ce2ff1..1c2ba958 100644
--- a/samples/native-image-sample/pom.xml
+++ b/samples/native-image-sample/pom.xml
@@ -61,13 +61,13 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-pubsub</artifactId>
-      <version>1.129.2</version>
+      <version>1.129.3</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-storage-control</artifactId>
-      <version>2.37.1-beta-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage-control:current} -->
+      <version>2.38.1-beta-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage-control:current} -->
       <scope>compile</scope>
     </dependency>
   </dependencies>
diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml
index 050692d3..31ad6932 100644
--- a/samples/snapshot/pom.xml
+++ b/samples/snapshot/pom.xml
@@ -28,12 +28,12 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-storage</artifactId>
-      <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+      <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
     </dependency>
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-storage-control</artifactId>
-      <version>2.37.1-beta-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage-control:current} -->
+      <version>2.38.1-beta-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage-control:current} -->
       <scope>compile</scope>
     </dependency>
 
@@ -58,7 +58,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-pubsub</artifactId>
-      <version>1.129.2</version>
+      <version>1.129.3</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index f87f8d33..d27887a4 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -76,7 +76,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-pubsub</artifactId>
-      <version>1.129.2</version>
+      <version>1.129.3</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/storage-shared-benchmarking/pom.xml b/storage-shared-benchmarking/pom.xml
index cb946f88..2314abf3 100644
--- a/storage-shared-benchmarking/pom.xml
+++ b/storage-shared-benchmarking/pom.xml
@@ -10,7 +10,7 @@
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>google-cloud-storage-parent</artifactId>
-    <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+    <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
   </parent>
 
   <properties>
@@ -22,7 +22,7 @@
     <dependency>
       <groupId>info.picocli</groupId>
       <artifactId>picocli</artifactId>
-      <version>4.7.5</version>
+      <version>4.7.6</version>
     </dependency>
     <dependency>
       <groupId>com.google.cloud</groupId>
@@ -31,7 +31,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-storage</artifactId>
-      <version>2.37.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+      <version>2.38.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
       <classifier>tests</classifier>
     </dependency>
     <dependency>
diff --git a/versions.txt b/versions.txt
index 05d60af9..bb209224 100644
--- a/versions.txt
+++ b/versions.txt
@@ -1,10 +1,10 @@
 # Format:
 # module:released-version:current-version
 
-google-cloud-storage:2.37.0:2.37.1-SNAPSHOT
-gapic-google-cloud-storage-v2:2.37.0-alpha:2.37.1-alpha-SNAPSHOT
-grpc-google-cloud-storage-v2:2.37.0-alpha:2.37.1-alpha-SNAPSHOT
-proto-google-cloud-storage-v2:2.37.0-alpha:2.37.1-alpha-SNAPSHOT
-google-cloud-storage-control:2.37.0-beta:2.37.1-beta-SNAPSHOT
-proto-google-cloud-storage-control-v2:2.37.0-beta:2.37.1-beta-SNAPSHOT
-grpc-google-cloud-storage-control-v2:2.37.0-beta:2.37.1-beta-SNAPSHOT
+google-cloud-storage:2.38.0:2.38.1-SNAPSHOT
+gapic-google-cloud-storage-v2:2.38.0-alpha:2.38.1-alpha-SNAPSHOT
+grpc-google-cloud-storage-v2:2.38.0-alpha:2.38.1-alpha-SNAPSHOT
+proto-google-cloud-storage-v2:2.38.0-alpha:2.38.1-alpha-SNAPSHOT
+google-cloud-storage-control:2.38.0-beta:2.38.1-beta-SNAPSHOT
+proto-google-cloud-storage-control-v2:2.38.0-beta:2.38.1-beta-SNAPSHOT
+grpc-google-cloud-storage-control-v2:2.38.0-beta:2.38.1-beta-SNAPSHOT


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ResumableSessionFailureScenarioTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ResumableSessionFailureScenarioTest.java
index 0a53a4bb..4c17e19c 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ResumableSessionFailureScenarioTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ResumableSessionFailureScenarioTest.java
@@ -16,8 +16,12 @@
 
 package com.google.cloud.storage;
 
+import static com.google.cloud.storage.ByteSizeConstants._256KiB;
+import static com.google.cloud.storage.ByteSizeConstants._512KiB;
+import static com.google.cloud.storage.ResumableSessionFailureScenario.SCENARIO_1;
 import static com.google.cloud.storage.ResumableSessionFailureScenario.isContinue;
 import static com.google.cloud.storage.ResumableSessionFailureScenario.isOk;
+import static com.google.cloud.storage.TestUtils.assertAll;
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.api.client.http.EmptyContent;
@@ -26,8 +30,24 @@ import com.google.api.client.http.HttpRequest;
 import com.google.api.client.http.HttpResponse;
 import com.google.api.client.json.gson.GsonFactory;
 import com.google.api.client.testing.http.MockHttpTransport;
+import com.google.api.gax.grpc.GrpcCallContext;
+import com.google.api.gax.grpc.GrpcStatusCode;
+import com.google.api.gax.rpc.ApiException;
+import com.google.api.gax.rpc.ApiExceptionFactory;
 import com.google.api.services.storage.model.StorageObject;
+import com.google.cloud.storage.it.ChecksummedTestContent;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.protobuf.ByteString;
+import com.google.storage.v2.ChecksummedData;
+import com.google.storage.v2.Object;
+import com.google.storage.v2.ObjectChecksums;
+import com.google.storage.v2.WriteObjectRequest;
+import com.google.storage.v2.WriteObjectResponse;
+import io.grpc.Metadata;
+import io.grpc.Status.Code;
+import io.grpc.StatusRuntimeException;
+import io.grpc.internal.GrpcUtil;
 import java.io.IOException;
 import java.math.BigInteger;
 import java.nio.charset.StandardCharsets;
@@ -158,6 +178,117 @@ public final class ResumableSessionFailureScenarioTest {
     assertThat(storageException).hasMessageThat().contains("|< x-goog-gcs-idempotency-token: 5");
   }
 
+  @Test
+  public void grpc_response() throws Exception {
+    ChecksummedTestContent content =
+        ChecksummedTestContent.of(DataGenerator.base64Characters().genBytes(_256KiB));
+    WriteObjectRequest req1 =
+        WriteObjectRequest.newBuilder()
+            .setUploadId("uploadId")
+            .setChecksummedData(
+                ChecksummedData.newBuilder()
+                    .setContent(
+                        ByteString.copyFrom(DataGenerator.base64Characters().genBytes(_256KiB)))
+                    .build())
+            .build();
+    WriteObjectRequest req2 =
+        WriteObjectRequest.newBuilder()
+            .setWriteOffset(_256KiB)
+            .setChecksummedData(
+                ChecksummedData.newBuilder()
+                    .setContent(ByteString.copyFrom(content.getBytes()))
+                    .setCrc32C(content.getCrc32c())
+                    .build())
+            .build();
+    WriteObjectRequest req3 =
+        WriteObjectRequest.newBuilder()
+            .setWriteOffset(_512KiB)
+            .setFinishWrite(true)
+            .setObjectChecksums(
+                ObjectChecksums.newBuilder()
+                    .setCrc32C(345)
+                    .setMd5Hash(ByteString.copyFromUtf8("asdf"))
+                    .build())
+            .build();
+    WriteObjectResponse resp1 =
+        WriteObjectResponse.newBuilder()
+            .setResource(Object.newBuilder().setName("obj").setSize(_512KiB).build())
+            .build();
+    GrpcCallContext context =
+        GrpcCallContext.createDefault()
+            .withExtraHeaders(
+                ImmutableMap.of(
+                    "x-goog-request-params",
+                    ImmutableList.of("bucket=projects/_/bucket/bucket-name")));
+    StorageException se =
+        SCENARIO_1.toStorageException(ImmutableList.of(req1, req2, req3), resp1, context, null);
+    assertAll(
+        () ->
+            assertThat(se)
+                .hasMessageThat()
+                .contains("x-goog-request-params: bucket=projects/_/bucket/bucket-name"),
+        () -> assertThat(se).hasMessageThat().contains("upload_id: "),
+        () -> assertThat(se).hasMessageThat().contains("0:262144"),
+        () -> assertThat(se).hasMessageThat().contains(", crc32c: "), // from ChecksummedData
+        () -> assertThat(se).hasMessageThat().contains("write_offset: "),
+        () -> assertThat(se).hasMessageThat().contains("finish_write: "),
+        () -> assertThat(se).hasMessageThat().contains("object_checksums: "),
+        () -> assertThat(se).hasMessageThat().contains("crc32c: "), // from object_checksums
+        () -> assertThat(se).hasMessageThat().contains("md5_hash: "),
+        () -> assertThat(se).hasMessageThat().contains("resource {"));
+  }
+
+  @Test
+  public void grpc_apiException() throws Exception {
+    WriteObjectRequest req1 =
+        WriteObjectRequest.newBuilder()
+            .setUploadId("uploadId")
+            .setChecksummedData(
+                ChecksummedData.newBuilder()
+                    .setContent(
+                        ByteString.copyFrom(DataGenerator.base64Characters().genBytes(_256KiB)))
+                    .build())
+            .build();
+    GrpcCallContext context = Retrying.newCallContext();
+    Code code = Code.FAILED_PRECONDITION;
+    Metadata trailers = new Metadata();
+    trailers.put(GrpcUtil.USER_AGENT_KEY, "test-class/");
+    StatusRuntimeException statusRuntimeException =
+        code.toStatus().withDescription("precondition did not hold").asRuntimeException(trailers);
+    ApiException apiException =
+        ApiExceptionFactory.createException(statusRuntimeException, GrpcStatusCode.of(code), true);
+
+    StorageException se =
+        SCENARIO_1.toStorageException(ImmutableList.of(req1), null, context, apiException);
+    assertAll(
+        () -> assertThat(se).hasMessageThat().contains("upload_id: "),
+        () -> assertThat(se).hasMessageThat().contains("0:262144"),
+        () -> assertThat(se).hasMessageThat().doesNotContain("WriteObjectResponse"),
+        () -> assertThat(se).hasMessageThat().contains("Status{code=FAILED_PRECONDITION"),
+        () -> assertThat(se).hasMessageThat().contains("user-agent=test-class/"));
+  }
+
+  @Test
+  public void grpc_nonApiException() throws Exception {
+    WriteObjectRequest req1 =
+        WriteObjectRequest.newBuilder()
+            .setUploadId("uploadId")
+            .setChecksummedData(
+                ChecksummedData.newBuilder()
+                    .setContent(
+                        ByteString.copyFrom(DataGenerator.base64Characters().genBytes(_256KiB)))
+                    .build())
+            .build();
+    GrpcCallContext context = Retrying.newCallContext();
+    Cause cause = new Cause();
+    StorageException se =
+        SCENARIO_1.toStorageException(ImmutableList.of(req1), null, context, cause);
+    assertAll(
+        () -> assertThat(se).hasMessageThat().contains("upload_id: "),
+        () -> assertThat(se).hasMessageThat().contains("0:262144"),
+        () -> assertThat(se).hasMessageThat().doesNotContain("WriteObjectResponse"));
+  }
+
   private static final class Cause extends RuntimeException {
 
     private Cause() {
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/GrpcPlainRequestLoggingInterceptor.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/GrpcPlainRequestLoggingInterceptor.java
new file mode 100644
index 00000000..7116108d
--- /dev/null
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/GrpcPlainRequestLoggingInterceptor.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2024 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.google.cloud.storage.it;
+
+import com.google.api.gax.grpc.GrpcInterceptorProvider;
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.MessageOrBuilder;
+import com.google.storage.v2.WriteObjectRequest;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
+import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+/**
+ * Client side interceptor which will log gRPC request, response and status messages in plain text,
+ * rather than the byte encoded text io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler does.
+ *
+ * <p>This interceptor does not include the other useful information that NettyClientHandler
+ * provides such as headers, method names, peers etc.
+ */
+public final class GrpcPlainRequestLoggingInterceptor implements ClientInterceptor {
+
+  private static final Logger LOGGER =
+      Logger.getLogger(GrpcPlainRequestLoggingInterceptor.class.getName());
+
+  private static final GrpcPlainRequestLoggingInterceptor INSTANCE =
+      new GrpcPlainRequestLoggingInterceptor();
+
+  private GrpcPlainRequestLoggingInterceptor() {}
+
+  public static GrpcPlainRequestLoggingInterceptor getInstance() {
+    return INSTANCE;
+  }
+
+  public static GrpcInterceptorProvider getInterceptorProvider() {
+    return InterceptorProvider.INSTANCE;
+  }
+
+  @Override
+  public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+      MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+    ClientCall<ReqT, RespT> call = next.newCall(method, callOptions);
+    return new SimpleForwardingClientCall<ReqT, RespT>(call) {
+      @Override
+      public void start(Listener<RespT> responseListener, Metadata headers) {
+        SimpleForwardingClientCallListener<RespT> listener =
+            new SimpleForwardingClientCallListener<RespT>(responseListener) {
+              @Override
+              public void onMessage(RespT message) {
+                LOGGER.log(
+                    Level.CONFIG,
+                    () ->
+                        String.format(
+                            "<<< %s{%n%s}", message.getClass().getSimpleName(), fmtProto(message)));
+                super.onMessage(message);
+              }
+
+              @Override
+              public void onClose(Status status, Metadata trailers) {
+                LOGGER.log(
+                    Level.CONFIG,
+                    () ->
+                        String.format(
+                            "<<< status = %s, trailers = %s",
+                            status.toString(), trailers.toString()));
+                super.onClose(status, trailers);
+              }
+            };
+        super.start(listener, headers);
+      }
+
+      @Override
+      public void sendMessage(ReqT message) {
+        LOGGER.log(
+            Level.CONFIG,
+            () ->
+                String.format(
+                    ">>> %s{%n%s}", message.getClass().getSimpleName(), fmtProto(message)));
+        super.sendMessage(message);
+      }
+    };
+  }
+
+  @NonNull
+  static String fmtProto(@NonNull Object obj) {
+    if (obj instanceof WriteObjectRequest) {
+      return fmtProto((WriteObjectRequest) obj);
+    } else if (obj instanceof MessageOrBuilder) {
+      return fmtProto((MessageOrBuilder) obj);
+    } else {
+      return obj.toString();
+    }
+  }
+
+  @NonNull
+  static String fmtProto(@NonNull final MessageOrBuilder msg) {
+    return msg.toString();
+  }
+
+  @NonNull
+  static String fmtProto(@NonNull WriteObjectRequest msg) {
+    if (msg.hasChecksummedData()) {
+      ByteString content = msg.getChecksummedData().getContent();
+      if (content.size() > 20) {
+        WriteObjectRequest.Builder b = msg.toBuilder();
+        ByteString snip = ByteString.copyFromUtf8(String.format("<snip (%d)>", content.size()));
+        ByteString trim = content.substring(0, 20).concat(snip);
+        b.getChecksummedDataBuilder().setContent(trim);
+
+        return b.build().toString();
+      }
+    }
+    return msg.toString();
+  }
+
+  private static final class InterceptorProvider implements GrpcInterceptorProvider {
+    private static final InterceptorProvider INSTANCE = new InterceptorProvider();
+
+    private final List<ClientInterceptor> interceptors;
+
+    private InterceptorProvider() {
+      this.interceptors = ImmutableList.of(GrpcPlainRequestLoggingInterceptor.INSTANCE);
+    }
+
+    @Override
+    public List<ClientInterceptor> getInterceptors() {
+      return interceptors;
+    }
+  }
+}
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/BackendResources.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/BackendResources.java
index 5f307c76..8aab9ba6 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/BackendResources.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/BackendResources.java
@@ -26,6 +26,7 @@ import com.google.cloud.storage.BucketInfo;
 import com.google.cloud.storage.Storage;
 import com.google.cloud.storage.StorageOptions;
 import com.google.cloud.storage.TransportCompatibility.Transport;
+import com.google.cloud.storage.it.GrpcPlainRequestLoggingInterceptor;
 import com.google.cloud.storage.it.runner.annotations.Backend;
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
@@ -119,12 +120,17 @@ final class BackendResources implements ManagedLifecycle {
                 new StorageInstance(
                     backend == Backend.TEST_BENCH
                         ? StorageOptions.grpc()
+                            .setGrpcInterceptorProvider(
+                                GrpcPlainRequestLoggingInterceptor.getInterceptorProvider())
                             .setCredentials(NoCredentials.getInstance())
                             // TODO: improve this
                             .setHost(Registry.getInstance().testBench().getGRPCBaseUri())
                             .setProjectId("test-project-id")
                             .build()
-                        : StorageOptions.grpc().build(),
+                        : StorageOptions.grpc()
+                            .setGrpcInterceptorProvider(
+                                GrpcPlainRequestLoggingInterceptor.getInterceptorProvider())
+                            .build(),
                     protectedBucketNames));
     TestRunScopedInstance<BucketInfoShim> bucket =
         TestRunScopedInstance.of(