Repository: googleapis/java-storage
Pull request #2567 (2024-05-28 20:48:31)

fix: update grpc single-shot uploads to validate ack'd object size
Follow up to #2527

This updated errors from grpc single-shot uploads to have messages that include useful debugging information.

When a response is received but doesn't validate with the expected state:
```
com.google.cloud.storage.StorageException: Finalized upload, but object size less than expected.
	|> [
	|> 	com.google.storage.v2.WriteObjectRequest{
	|> 		write_offset: 524288
	|> 		finish_write: true
	|> 	}
	|> ]
	|
	|< com.google.storage.v2.WriteObjectResponse{
	|< 	resource {
	|< 	  name: "obj"
	|< 	  size: 262144
	|< 	}
	|< }
	|
```




Patch:
diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml
index 5db36a5f..359fe71c 100644
--- a/.github/.OwlBot.lock.yaml
+++ b/.github/.OwlBot.lock.yaml
@@ -13,5 +13,5 @@
 # limitations under the License.
 docker:
   image: gcr.io/cloud-devrel-public-resources/owlbot-java:latest
-  digest: sha256:68ba5f5164a4b55529d358bb262feaa000536a0c62980727dd05a91bbb47ea5e
-# created: 2024-05-09T16:31:37.168667071Z
+  digest: sha256:72f0d373307d128b2cb720c5cb4d90b31f0e86529dd138c632710ae0c69efae3
+# created: 2024-06-05T18:32:21.724930324Z
diff --git a/.github/workflows/unmanaged_dependency_check.yaml b/.github/workflows/unmanaged_dependency_check.yaml
index 50b31a20..0149c56d 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.30.1
+        uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.31.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 c283c89e..39531095 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.30.1"
+  value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.31.0"
 }
 
 env_vars: {
diff --git a/.kokoro/presubmit/graalvm-native.cfg b/.kokoro/presubmit/graalvm-native.cfg
index c997432f..c8748d5b 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.30.1"
+  value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.31.0"
 }
 
 env_vars: {
diff --git a/.readme-partials.yaml b/.readme-partials.yaml
index 2f8c71d3..a04ad1a0 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.39.0-beta</version><!-- {x-version-update:google-cloud-storage-control:current} -->
+      <version>2.40.0</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:2.39.0')
+  implementation platform('com.google.cloud:libraries-bom:2.40.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.39.0-beta' <!-- {x-version-update:google-cloud-storage-control:current} -->
+  implementation 'com.google.cloud:google-cloud-storage-control:2.40.0' <!-- {x-version-update:google-cloud-storage-control:current} -->
   ```
   
   #### Creating an authorized service object
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4d94b38a..33a2b4b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,29 @@
 # Changelog
 
+## [2.40.0](https://github.com/googleapis/java-storage/compare/v2.39.0...v2.40.0) (2024-06-06)
+
+
+### Features
+
+* Promote google-cloud-storage-control to GA ([#2575](https://github.com/googleapis/java-storage/issues/2575)) ([129f188](https://github.com/googleapis/java-storage/commit/129f188a9877b3bd71db3b00ba3d8d6e4095fb67))
+
+
+### Bug Fixes
+
+* Reduce Java 21 Virtual Thread Pinning in IO operations ([#2553](https://github.com/googleapis/java-storage/issues/2553)) ([498fd0b](https://github.com/googleapis/java-storage/commit/498fd0b391c9d1d01c453fe31abea7890c0b498f))
+
+
+### Dependencies
+
+* Update dependency com.google.cloud:sdk-platform-java-config to v3.31.0 ([#2571](https://github.com/googleapis/java-storage/issues/2571)) ([67ce3d6](https://github.com/googleapis/java-storage/commit/67ce3d6505e1e48f496ab52884f5f5c2852aa445))
+* Update dependency net.jqwik:jqwik to v1.8.5 ([#2563](https://github.com/googleapis/java-storage/issues/2563)) ([88f7d86](https://github.com/googleapis/java-storage/commit/88f7d86d960aa6986cf41165d80d03a551502ec2))
+
+
+### Documentation
+
+* Managed Folders samples ([#2562](https://github.com/googleapis/java-storage/issues/2562)) ([5ffc1f2](https://github.com/googleapis/java-storage/commit/5ffc1f2fa75ac2e1c0022301e02b6ea6627faf03))
+* Update javadoc for createFrom ([#2522](https://github.com/googleapis/java-storage/issues/2522)) ([dc31e95](https://github.com/googleapis/java-storage/commit/dc31e95158e66d0eb4c85122edd29431a610a204))
+
 ## [2.39.0](https://github.com/googleapis/java-storage/compare/v2.38.0...v2.39.0) (2024-05-22)
 
 
diff --git a/README.md b/README.md
index 1a1f2b9d..b0deb965 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ If you are using Maven with [BOM][libraries-bom], add this to your pom.xml file:
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>libraries-bom</artifactId>
-      <version>26.39.0</version>
+      <version>26.40.0</version>
       <type>pom</type>
       <scope>import</scope>
     </dependency>
@@ -46,12 +46,12 @@ If you are using Maven without the BOM, add this to your dependencies:
 <dependency>
   <groupId>com.google.cloud</groupId>
   <artifactId>google-cloud-storage</artifactId>
-  <version>2.38.0</version>
+  <version>2.39.0</version>
 </dependency>
 <dependency>
   <groupId>com.google.cloud</groupId>
   <artifactId>google-cloud-storage-control</artifactId>
-  <version>2.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+  <version>2.40.0</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.39.0')
+implementation platform('com.google.cloud:libraries-bom:26.40.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.38.0'
+implementation 'com.google.cloud:google-cloud-storage:2.39.0'
 ```
 
 If you are using SBT, add this to your dependencies:
 
 ```Scala
-libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "2.38.0"
+libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "2.39.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.39.0-beta</version><!-- {x-version-update:google-cloud-storage-control:current} -->
+    <version>2.40.0</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:2.39.0')
+implementation platform('com.google.cloud:libraries-bom:2.40.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.39.0-beta' <!-- {x-version-update:google-cloud-storage-control:current} -->
+implementation 'com.google.cloud:google-cloud-storage-control:2.40.0' <!-- {x-version-update:google-cloud-storage-control:current} -->
 ```
 
 #### Creating an authorized service object
@@ -349,6 +349,10 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-storage/tree/
 | Delete Hmac Key | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/hmac/DeleteHmacKey.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/hmac/DeleteHmacKey.java) |
 | Get Hmac Key | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/hmac/GetHmacKey.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/hmac/GetHmacKey.java) |
 | List Hmac Keys | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/hmac/ListHmacKeys.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/hmac/ListHmacKeys.java) |
+| Create Managed Folder | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/managedfolders/CreateManagedFolder.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/managedfolders/CreateManagedFolder.java) |
+| Delete Managed Folder | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/managedfolders/DeleteManagedFolder.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/managedfolders/DeleteManagedFolder.java) |
+| Get Managed Folder | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/managedfolders/GetManagedFolder.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/managedfolders/GetManagedFolder.java) |
+| List Managed Folders | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/managedfolders/ListManagedFolders.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/managedfolders/ListManagedFolders.java) |
 | Add File Owner | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/AddFileOwner.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/AddFileOwner.java) |
 | Batch Set Object Metadata | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/BatchSetObjectMetadata.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/BatchSetObjectMetadata.java) |
 | Change Object Csek To Kms | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ChangeObjectCsekToKms.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ChangeObjectCsekToKms.java) |
@@ -494,7 +498,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.38.0
+[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-storage/2.39.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 fcc442cd..32201798 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.39.0-alpha</version><!-- {x-version-update:gapic-google-cloud-storage-v2:current} -->
+  <version>2.40.0-alpha</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.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+    <version>2.40.0</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 0c56765c..c5250748 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.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+  <version>2.40.0</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.30.1</version>
+    <version>3.31.0</version>
     <relativePath/>
   </parent>
 
@@ -69,37 +69,37 @@
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>google-cloud-storage</artifactId>
-        <version>2.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+        <version>2.40.0</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.39.0-alpha</version><!-- {x-version-update:gapic-google-cloud-storage-v2:current} -->
+        <version>2.40.0-alpha</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.39.0-alpha</version><!-- {x-version-update:grpc-google-cloud-storage-v2:current} -->
+        <version>2.40.0-alpha</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.39.0-alpha</version><!-- {x-version-update:proto-google-cloud-storage-v2:current} -->
+        <version>2.40.0-alpha</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.39.0-beta</version><!-- {x-version-update:google-cloud-storage-control:current} -->
+        <version>2.40.0</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.39.0-beta</version><!-- {x-version-update:grpc-google-cloud-storage-control-v2:current} -->
+        <version>2.40.0</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.39.0-beta</version><!-- {x-version-update:proto-google-cloud-storage-control-v2:current} -->
+        <version>2.40.0</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 f1f24952..a9b22062 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.39.0-beta</version><!-- {x-version-update:google-cloud-storage-control:current} -->
+    <version>2.40.0</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.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+        <version>2.40.0</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 213bebcd..b9d33513 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.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+  <version>2.40.0</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.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+    <version>2.40.0</version><!-- {x-version-update:google-cloud-storage:current} -->
   </parent>
   <properties>
     <site.installationModule>google-cloud-storage</site.installationModule>
-    <pubsub-proto.version>1.111.5</pubsub-proto.version>
+    <pubsub-proto.version>1.112.0</pubsub-proto.version>
     <junit-platform.version>5.10.2</junit-platform.version>
   </properties>
   <dependencies>
@@ -181,13 +181,13 @@
     <dependency>
       <groupId>com.google.api.grpc</groupId>
       <artifactId>proto-google-cloud-kms-v1</artifactId>
-      <version>0.137.0</version>
+      <version>0.138.0</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-kms</artifactId>
-      <version>2.46.0</version>
+      <version>2.47.0</version>
       <scope>test</scope>
     </dependency>
     <dependency>
@@ -251,7 +251,7 @@
     <dependency>
       <groupId>net.jqwik</groupId>
       <artifactId>jqwik</artifactId>
-      <version>1.8.4</version>
+      <version>1.8.5</version>
       <scope>test</scope>
     </dependency>
     <dependency>
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/AsyncAppendingQueue.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/AsyncAppendingQueue.java
index 644dd014..a00c8d03 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/AsyncAppendingQueue.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/AsyncAppendingQueue.java
@@ -38,6 +38,7 @@ import java.util.PriorityQueue;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
 import java.util.stream.Collectors;
 import org.checkerframework.checker.nullness.qual.NonNull;
 
@@ -66,6 +67,7 @@ final class AsyncAppendingQueue<@NonNull T> implements AutoCloseable {
   private final AtomicReference<Throwable> shortCircuitFailure;
   private final ApiFutureCallback<T> shortCircuitRegistrationCallback;
 
+  private final ReentrantLock lock;
   private volatile State state;
 
   private AsyncAppendingQueue(
@@ -85,26 +87,32 @@ final class AsyncAppendingQueue<@NonNull T> implements AutoCloseable {
                 shortCircuitFailure.compareAndSet(null, throwable);
               }
             };
+    lock = new ReentrantLock();
   }
 
-  synchronized AsyncAppendingQueue<T> append(ApiFuture<T> value) throws ShortCircuitException {
-    checkState(state.isOpen(), "already closed");
-    Throwable throwable = shortCircuitFailure.get();
-    if (throwable != null) {
-      ShortCircuitException shortCircuitException = new ShortCircuitException(throwable);
-      finalResult.cancel(true);
-      throw shortCircuitException;
+  AsyncAppendingQueue<T> append(ApiFuture<T> value) throws ShortCircuitException {
+    lock.lock();
+    try {
+      checkState(state.isOpen(), "already closed");
+      Throwable throwable = shortCircuitFailure.get();
+      if (throwable != null) {
+        ShortCircuitException shortCircuitException = new ShortCircuitException(throwable);
+        finalResult.cancel(true);
+        throw shortCircuitException;
+      }
+      checkNotNull(value, "value must not be null");
+
+      Element<T> newElement = newElement(value);
+      queue.offer(newElement);
+      boolean isFull = queue.size() == maxElementsPerCompact;
+      if (isFull) {
+        Element<T> compact = compact(exec);
+        queue.offer(compact);
+      }
+      return this;
+    } finally {
+      lock.unlock();
     }
-    checkNotNull(value, "value must not be null");
-
-    Element<T> newElement = newElement(value);
-    queue.offer(newElement);
-    boolean isFull = queue.size() == maxElementsPerCompact;
-    if (isFull) {
-      Element<T> compact = compact(exec);
-      queue.offer(compact);
-    }
-    return this;
   }
 
   ApiFuture<T> getResult() {
@@ -116,35 +124,40 @@ final class AsyncAppendingQueue<@NonNull T> implements AutoCloseable {
   }
 
   @Override
-  public synchronized void close() {
-    if (!state.isOpen()) {
-      return;
-    }
-    state = State.CLOSING;
-
-    if (queue.isEmpty()) {
-      NoSuchElementException neverAppendedTo = new NoSuchElementException("Never appended to");
-      finalResult.setException(neverAppendedTo);
-      throw neverAppendedTo;
-    } else {
-      Element<T> transform = compact(exec);
-
-      ApiFutures.addCallback(
-          transform.getValue(),
-          new ApiFutureCallback<T>() {
-            @Override
-            public void onFailure(Throwable err) {
-              finalResult.setException(err);
-            }
-
-            @Override
-            public void onSuccess(T t) {
-              finalResult.set(t);
-            }
-          },
-          exec);
+  public void close() {
+    lock.lock();
+    try {
+      if (!state.isOpen()) {
+        return;
+      }
+      state = State.CLOSING;
+
+      if (queue.isEmpty()) {
+        NoSuchElementException neverAppendedTo = new NoSuchElementException("Never appended to");
+        finalResult.setException(neverAppendedTo);
+        throw neverAppendedTo;
+      } else {
+        Element<T> transform = compact(exec);
+
+        ApiFutures.addCallback(
+            transform.getValue(),
+            new ApiFutureCallback<T>() {
+              @Override
+              public void onFailure(Throwable err) {
+                finalResult.setException(err);
+              }
+
+              @Override
+              public void onSuccess(T t) {
+                finalResult.set(t);
+              }
+            },
+            exec);
+      }
+      state = State.CLOSED;
+    } finally {
+      lock.unlock();
     }
-    state = State.CLOSED;
   }
 
   @NonNull
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseStorageReadChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseStorageReadChannel.java
index b6b0a76b..465d3d6a 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseStorageReadChannel.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseStorageReadChannel.java
@@ -29,6 +29,7 @@ import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.ReadableByteChannel;
+import java.util.concurrent.locks.ReentrantLock;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 abstract class BaseStorageReadChannel<T> implements StorageReadChannel {
@@ -41,42 +42,64 @@ abstract class BaseStorageReadChannel<T> implements StorageReadChannel {
   private int chunkSize = _2MiB;
   private BufferHandle bufferHandle;
   private LazyReadChannel<?, T> lazyReadChannel;
+  protected final ReentrantLock lock;
 
   protected BaseStorageReadChannel(Decoder<T, BlobInfo> objectDecoder) {
     this.objectDecoder = objectDecoder;
     this.result = SettableApiFuture.create();
     this.open = true;
     this.byteRangeSpec = ByteRangeSpec.nullRange();
+    this.lock = new ReentrantLock();
   }
 
   @Override
-  public final synchronized void setChunkSize(int chunkSize) {
-    StorageException.wrapIOException(() -> maybeResetChannel(true));
-    this.chunkSize = chunkSize;
+  public final void setChunkSize(int chunkSize) {
+    lock.lock();
+    try {
+      StorageException.wrapIOException(() -> maybeResetChannel(true));
+      this.chunkSize = chunkSize;
+    } finally {
+      lock.unlock();
+    }
   }
 
   @Override
-  public final synchronized boolean isOpen() {
-    return open;
+  public final boolean isOpen() {
+    lock.lock();
+    try {
+      return open;
+    } finally {
+      lock.unlock();
+    }
   }
 
   @Override
-  public final synchronized void close() {
-    open = false;
-    if (internalGetLazyChannel().isOpen()) {
-      ReadableByteChannel channel = internalGetLazyChannel().getChannel();
-      StorageException.wrapIOException(channel::close);
+  public final void close() {
+    lock.lock();
+    try {
+      open = false;
+      if (internalGetLazyChannel().isOpen()) {
+        ReadableByteChannel channel = internalGetLazyChannel().getChannel();
+        StorageException.wrapIOException(channel::close);
+      }
+    } finally {
+      lock.unlock();
     }
   }
 
   @Override
-  public final synchronized StorageReadChannel setByteRangeSpec(ByteRangeSpec byteRangeSpec) {
+  public final StorageReadChannel setByteRangeSpec(ByteRangeSpec byteRangeSpec) {
     requireNonNull(byteRangeSpec, "byteRangeSpec must be non null");
-    if (!this.byteRangeSpec.equals(byteRangeSpec)) {
-      StorageException.wrapIOException(() -> maybeResetChannel(false));
-      this.byteRangeSpec = byteRangeSpec;
+    lock.lock();
+    try {
+      if (!this.byteRangeSpec.equals(byteRangeSpec)) {
+        StorageException.wrapIOException(() -> maybeResetChannel(false));
+        this.byteRangeSpec = byteRangeSpec;
+      }
+      return this;
+    } finally {
+      lock.unlock();
     }
-    return this;
   }
 
   @Override
@@ -85,42 +108,47 @@ abstract class BaseStorageReadChannel<T> implements StorageReadChannel {
   }
 
   @Override
-  public final synchronized int read(ByteBuffer dst) throws IOException {
-    // BlobReadChannel only considered itself closed if close had been called on it.
-    if (!open) {
-      throw new ClosedChannelException();
-    }
-    long diff = byteRangeSpec.length();
-    // the check on beginOffset >= 0 used to be a precondition on seek(long)
-    // move it here to preserve existing behavior while allowing new negative offsets
-    if (diff <= 0 && byteRangeSpec.beginOffset() >= 0) {
-      return -1;
-    }
+  public final int read(ByteBuffer dst) throws IOException {
+    lock.lock();
     try {
-      // trap if the fact that tmp is already closed, and instead return -1
-      ReadableByteChannel tmp = internalGetLazyChannel().getChannel();
-      if (!tmp.isOpen()) {
-        return -1;
-      }
-      int read = tmp.read(dst);
-      if (read != -1) {
-        byteRangeSpec = byteRangeSpec.withShiftBeginOffset(read);
+      // BlobReadChannel only considered itself closed if close had been called on it.
+      if (!open) {
+        throw new ClosedChannelException();
       }
-      return read;
-    } catch (StorageException e) {
-      if (e.getCode() == 416) {
-        // HttpStorageRpc turns 416 into a null etag with an empty byte array, leading
-        // BlobReadChannel to believe it read 0 bytes, returning -1 and leaving the channel open.
-        // Emulate that same behavior here to preserve behavior compatibility, though this should
-        // be removed in the next major version.
+      long diff = byteRangeSpec.length();
+      // the check on beginOffset >= 0 used to be a precondition on seek(long)
+      // move it here to preserve existing behavior while allowing new negative offsets
+      if (diff <= 0 && byteRangeSpec.beginOffset() >= 0) {
         return -1;
-      } else {
-        throw new IOException(e);
       }
-    } catch (IOException e) {
-      throw e;
-    } catch (Exception e) {
-      throw new IOException(StorageException.coalesce(e));
+      try {
+        // trap if the fact that tmp is already closed, and instead return -1
+        ReadableByteChannel tmp = internalGetLazyChannel().getChannel();
+        if (!tmp.isOpen()) {
+          return -1;
+        }
+        int read = tmp.read(dst);
+        if (read != -1) {
+          byteRangeSpec = byteRangeSpec.withShiftBeginOffset(read);
+        }
+        return read;
+      } catch (StorageException e) {
+        if (e.getCode() == 416) {
+          // HttpStorageRpc turns 416 into a null etag with an empty byte array, leading
+          // BlobReadChannel to believe it read 0 bytes, returning -1 and leaving the channel open.
+          // Emulate that same behavior here to preserve behavior compatibility, though this should
+          // be removed in the next major version.
+          return -1;
+        } else {
+          throw new IOException(e);
+        }
+      } catch (IOException e) {
+        throw e;
+      } catch (Exception e) {
+        throw new IOException(StorageException.coalesce(e));
+      }
+    } finally {
+      lock.unlock();
     }
   }
 
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseStorageWriteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseStorageWriteChannel.java
index f5f07632..10f79c8d 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseStorageWriteChannel.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseStorageWriteChannel.java
@@ -30,12 +30,14 @@ import com.google.common.util.concurrent.MoreExecutors;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
+import java.util.concurrent.locks.ReentrantLock;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 abstract class BaseStorageWriteChannel<T> implements StorageWriteChannel {
 
   private final Decoder<T, BlobInfo> objectDecoder;
   private final SettableApiFuture<T> result;
+  protected final ReentrantLock lock;
 
   private long position;
   private boolean open;
@@ -55,27 +57,39 @@ abstract class BaseStorageWriteChannel<T> implements StorageWriteChannel {
   protected BaseStorageWriteChannel(Decoder<T, BlobInfo> objectDecoder) {
     this.objectDecoder = objectDecoder;
     this.result = SettableApiFuture.create();
+    this.lock = new ReentrantLock();
     this.open = true;
     this.chunkSize = _16MiB;
     this.writeCalledAtLeastOnce = false;
   }
 
   @Override
-  public final synchronized void setChunkSize(int chunkSize) {
-    Preconditions.checkArgument(chunkSize > 0, "chunkSize must be > 0, received %d", chunkSize);
-    Preconditions.checkState(
-        bufferHandle == null || bufferHandle.position() == 0,
-        "unable to change chunk size with data buffered");
-    this.chunkSize = chunkSize;
+  public final void setChunkSize(int chunkSize) {
+    lock.lock();
+    try {
+      Preconditions.checkArgument(chunkSize > 0, "chunkSize must be > 0, received %d", chunkSize);
+      Preconditions.checkState(
+          bufferHandle == null || bufferHandle.position() == 0,
+          "unable to change chunk size with data buffered");
+      this.chunkSize = chunkSize;
+    } finally {
+      lock.unlock();
+    }
   }
 
   @Override
-  public final synchronized boolean isOpen() {
-    return open;
+  public final boolean isOpen() {
+    lock.lock();
+    try {
+      return open;
+    } finally {
+      lock.unlock();
+    }
   }
 
   @Override
-  public final synchronized void close() throws IOException {
+  public final void close() throws IOException {
+    lock.lock();
     try {
       if (open && !writeCalledAtLeastOnce) {
         this.write(ByteBuffer.allocate(0));
@@ -85,28 +99,34 @@ abstract class BaseStorageWriteChannel<T> implements StorageWriteChannel {
       }
     } finally {
       open = false;
+      lock.unlock();
     }
   }
 
   @Override
-  public final synchronized int write(ByteBuffer src) throws IOException {
-    if (!open) {
-      throw new ClosedChannelException();
-    }
-    writeCalledAtLeastOnce = true;
+  public final int write(ByteBuffer src) throws IOException {
+    lock.lock();
     try {
-      BufferedWritableByteChannel tmp = internalGetLazyChannel().getChannel();
-      if (!tmp.isOpen()) {
-        return 0;
+      if (!open) {
+        throw new ClosedChannelException();
       }
-      int write = tmp.write(src);
-      return write;
-    } catch (StorageException e) {
-      throw new IOException(e);
-    } catch (IOException e) {
-      throw e;
-    } catch (Exception e) {
-      throw new IOException(StorageException.coalesce(e));
+      writeCalledAtLeastOnce = true;
+      try {
+        BufferedWritableByteChannel tmp = internalGetLazyChannel().getChannel();
+        if (!tmp.isOpen()) {
+          return 0;
+        }
+        int write = tmp.write(src);
+        return write;
+      } catch (StorageException e) {
+        throw new IOException(e);
+      } catch (IOException e) {
+        throw e;
+      } catch (Exception e) {
+        throw new IOException(StorageException.coalesce(e));
+      }
+    } finally {
+      lock.unlock();
     }
   }
 
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobReadChannelV2.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobReadChannelV2.java
index e8143871..6ff9ca6e 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobReadChannelV2.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobReadChannelV2.java
@@ -51,10 +51,15 @@ final class BlobReadChannelV2 extends BaseStorageReadChannel<StorageObject> {
   }
 
   @Override
-  public synchronized RestorableState<ReadChannel> capture() {
-    ApiaryReadRequest apiaryReadRequest = getApiaryReadRequest();
-    return new BlobReadChannelV2State(
-        apiaryReadRequest, blobReadChannelContext.getStorageOptions(), getChunkSize());
+  public RestorableState<ReadChannel> capture() {
+    lock.lock();
+    try {
+      ApiaryReadRequest apiaryReadRequest = getApiaryReadRequest();
+      return new BlobReadChannelV2State(
+          apiaryReadRequest, blobReadChannelContext.getStorageOptions(), getChunkSize());
+    } finally {
+      lock.unlock();
+    }
   }
 
   protected LazyReadChannel<?, StorageObject> newLazyReadChannel() {
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannelV2.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannelV2.java
index 8b9de3f6..37108c4d 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannelV2.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannelV2.java
@@ -38,27 +38,32 @@ final class BlobWriteChannelV2 extends BaseStorageWriteChannel<StorageObject> {
   }
 
   @Override
-  public synchronized RestorableState<WriteChannel> capture() {
-    final byte[] bufferSnapshot;
-    BufferHandle handle = getBufferHandle();
-    if (handle.position() > 0) {
-      ByteBuffer byteBuffer = handle.get();
-      // duplicate so we don't actually modify the existing instance
-      ByteBuffer dup = byteBuffer.duplicate();
-      dup.flip();
-      int remaining = dup.remaining();
-      bufferSnapshot = new byte[remaining];
-      dup.get(bufferSnapshot);
-    } else {
-      bufferSnapshot = new byte[0];
+  public RestorableState<WriteChannel> capture() {
+    lock.lock();
+    try {
+      final byte[] bufferSnapshot;
+      BufferHandle handle = getBufferHandle();
+      if (handle.position() > 0) {
+        ByteBuffer byteBuffer = handle.get();
+        // duplicate so we don't actually modify the existing instance
+        ByteBuffer dup = byteBuffer.duplicate();
+        dup.flip();
+        int remaining = dup.remaining();
+        bufferSnapshot = new byte[remaining];
+        dup.get(bufferSnapshot);
+      } else {
+        bufferSnapshot = new byte[0];
+      }
+      return new BlobWriteChannelV2State(
+          blobChannelContext.getStorageOptions(),
+          start,
+          getCommittedPosition(),
+          isOpen(),
+          getChunkSize(),
+          bufferSnapshot);
+    } finally {
+      lock.unlock();
     }
-    return new BlobWriteChannelV2State(
-        blobChannelContext.getStorageOptions(),
-        start,
-        getCommittedPosition(),
-        isOpen(),
-        getChunkSize(),
-        bufferSnapshot);
   }
 
   @Override
@@ -80,6 +85,7 @@ final class BlobWriteChannelV2 extends BaseStorageWriteChannel<StorageObject> {
 
   static final class BlobWriteChannelV2State
       implements RestorableState<WriteChannel>, Serializable {
+
     private static final long serialVersionUID = -1901664719924133474L;
 
     private final HttpStorageOptions options;
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedDirectWritableByteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedDirectWritableByteChannel.java
index 861e867d..5e67440a 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedDirectWritableByteChannel.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedDirectWritableByteChannel.java
@@ -20,12 +20,14 @@ 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.rpc.ApiException;
 import com.google.api.gax.rpc.ApiStreamObserver;
 import com.google.api.gax.rpc.ClientStreamingCallable;
 import com.google.cloud.storage.ChunkSegmenter.ChunkSegment;
 import com.google.cloud.storage.Crc32cValue.Crc32cLengthKnown;
 import com.google.cloud.storage.UnbufferedWritableByteChannelSession.UnbufferedWritableByteChannel;
 import com.google.cloud.storage.WriteCtx.SimpleWriteObjectRequestBuilderFactory;
+import com.google.common.collect.ImmutableList;
 import com.google.protobuf.ByteString;
 import com.google.storage.v2.ChecksummedData;
 import com.google.storage.v2.ObjectChecksums;
@@ -34,11 +36,7 @@ import com.google.storage.v2.WriteObjectResponse;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
-import java.util.function.LongConsumer;
 import org.checkerframework.checker.nullness.qual.NonNull;
 
 final class GapicUnbufferedDirectWritableByteChannel implements UnbufferedWritableByteChannel {
@@ -55,13 +53,14 @@ final class GapicUnbufferedDirectWritableByteChannel implements UnbufferedWritab
   private boolean open = true;
   private boolean first = true;
   private boolean finished = false;
+  private volatile WriteObjectRequest lastWrittenRequest;
 
   GapicUnbufferedDirectWritableByteChannel(
       SettableApiFuture<WriteObjectResponse> resultFuture,
       ChunkSegmenter chunkSegmenter,
       ClientStreamingCallable<WriteObjectRequest, WriteObjectResponse> write,
-      SimpleWriteObjectRequestBuilderFactory requestFactory) {
-    String bucketName = requestFactory.bucketName();
+      WriteCtx<SimpleWriteObjectRequestBuilderFactory> writeCtx) {
+    String bucketName = writeCtx.getRequestFactory().bucketName();
     this.resultFuture = resultFuture;
     this.chunkSegmenter = chunkSegmenter;
 
@@ -69,20 +68,63 @@ final class GapicUnbufferedDirectWritableByteChannel implements UnbufferedWritab
         contextWithBucketName(bucketName, GrpcCallContext.createDefault());
     this.write = write.withDefaultCallContext(internalContext);
 
-    this.writeCtx = new WriteCtx<>(requestFactory);
-    this.responseObserver = new Observer(writeCtx.getConfirmedBytes()::set, resultFuture::set);
+    this.writeCtx = writeCtx;
+    this.responseObserver = new Observer(internalContext);
   }
 
   @Override
   public long write(ByteBuffer[] srcs, int srcsOffset, int srcsLength) throws IOException {
-    return internalWrite(srcs, srcsOffset, srcsLength, false);
-  }
+    if (!open) {
+      throw new ClosedChannelException();
+    }
 
-  @Override
-  public long writeAndClose(ByteBuffer[] srcs, int srcsOffset, int srcsLength) throws IOException {
-    long write = internalWrite(srcs, srcsOffset, srcsLength, true);
-    close();
-    return write;
+    ChunkSegment[] data = chunkSegmenter.segmentBuffers(srcs, srcsOffset, srcsLength);
+    if (data.length == 0) {
+      return 0;
+    }
+
+    try {
+      ApiStreamObserver<WriteObjectRequest> openedStream = openedStream();
+      int bytesConsumed = 0;
+      for (ChunkSegment datum : data) {
+        Crc32cLengthKnown crc32c = datum.getCrc32c();
+        ByteString b = datum.getB();
+        int contentSize = b.size();
+        long offset = writeCtx.getTotalSentBytes().getAndAdd(contentSize);
+        Crc32cLengthKnown cumulative =
+            writeCtx
+                .getCumulativeCrc32c()
+                .accumulateAndGet(crc32c, chunkSegmenter.getHasher()::nullSafeConcat);
+        ChecksummedData.Builder checksummedData = ChecksummedData.newBuilder().setContent(b);
+        if (crc32c != null) {
+          checksummedData.setCrc32C(crc32c.getValue());
+        }
+        WriteObjectRequest.Builder builder = writeCtx.newRequestBuilder();
+        if (!first) {
+          builder.clearWriteObjectSpec();
+          builder.clearObjectChecksums();
+        }
+        builder.setWriteOffset(offset).setChecksummedData(checksummedData.build());
+        if (!datum.isOnlyFullBlocks()) {
+          builder.setFinishWrite(true);
+          if (cumulative != null) {
+            builder.setObjectChecksums(
+                ObjectChecksums.newBuilder().setCrc32C(cumulative.getValue()).build());
+          }
+          finished = true;
+        }
+
+        WriteObjectRequest build = builder.build();
+        first = false;
+        bytesConsumed += contentSize;
+        lastWrittenRequest = build;
+        openedStream.onNext(build);
+      }
+      return bytesConsumed;
+    } catch (RuntimeException e) {
+      resultFuture.setException(e);
+      throw e;
+    }
   }
 
   @Override
@@ -95,6 +137,7 @@ final class GapicUnbufferedDirectWritableByteChannel implements UnbufferedWritab
     ApiStreamObserver<WriteObjectRequest> openedStream = openedStream();
     if (!finished) {
       WriteObjectRequest message = finishMessage();
+      lastWrittenRequest = message;
       try {
         openedStream.onNext(message);
         openedStream.onCompleted();
@@ -115,79 +158,22 @@ final class GapicUnbufferedDirectWritableByteChannel implements UnbufferedWritab
     responseObserver.await();
   }
 
-  private long internalWrite(ByteBuffer[] srcs, int srcsOffset, int srcsLength, boolean finalize)
-      throws ClosedChannelException {
-    if (!open) {
-      throw new ClosedChannelException();
-    }
-
-    ChunkSegment[] data = chunkSegmenter.segmentBuffers(srcs, srcsOffset, srcsLength);
-
-    List<WriteObjectRequest> messages = new ArrayList<>();
-
-    ApiStreamObserver<WriteObjectRequest> openedStream = openedStream();
-    int bytesConsumed = 0;
-    for (ChunkSegment datum : data) {
-      Crc32cLengthKnown crc32c = datum.getCrc32c();
-      ByteString b = datum.getB();
-      int contentSize = b.size();
-      long offset = writeCtx.getTotalSentBytes().getAndAdd(contentSize);
-      Crc32cLengthKnown cumulative =
-          writeCtx
-              .getCumulativeCrc32c()
-              .accumulateAndGet(crc32c, chunkSegmenter.getHasher()::nullSafeConcat);
-      ChecksummedData.Builder checksummedData = ChecksummedData.newBuilder().setContent(b);
-      if (crc32c != null) {
-        checksummedData.setCrc32C(crc32c.getValue());
-      }
-      WriteObjectRequest.Builder builder =
-          writeCtx
-              .newRequestBuilder()
-              .setWriteOffset(offset)
-              .setChecksummedData(checksummedData.build());
-      if (!datum.isOnlyFullBlocks()) {
-        builder.setFinishWrite(true);
-        if (cumulative != null) {
-          builder.setObjectChecksums(
-              ObjectChecksums.newBuilder().setCrc32C(cumulative.getValue()).build());
-        }
-        finished = true;
-      }
-
-      WriteObjectRequest build = possiblyPairDownRequest(builder, first).build();
-      first = false;
-      messages.add(build);
-      bytesConsumed += contentSize;
-    }
-    if (finalize && !finished) {
-      messages.add(finishMessage());
-      finished = true;
-    }
-
-    try {
-      for (WriteObjectRequest message : messages) {
-        openedStream.onNext(message);
-      }
-    } catch (RuntimeException e) {
-      resultFuture.setException(e);
-      throw e;
-    }
-
-    return bytesConsumed;
-  }
-
   @NonNull
   private WriteObjectRequest finishMessage() {
     long offset = writeCtx.getTotalSentBytes().get();
     Crc32cLengthKnown crc32cValue = writeCtx.getCumulativeCrc32c().get();
 
-    WriteObjectRequest.Builder b =
-        writeCtx.newRequestBuilder().setFinishWrite(true).setWriteOffset(offset);
+    WriteObjectRequest.Builder b = writeCtx.newRequestBuilder();
+    if (!first) {
+      b.clearWriteObjectSpec();
+      b.clearObjectChecksums();
+      first = false;
+    }
+    b.setFinishWrite(true).setWriteOffset(offset);
     if (crc32cValue != null) {
       b.setObjectChecksums(ObjectChecksums.newBuilder().setCrc32C(crc32cValue.getValue()).build());
     }
-    WriteObjectRequest message = possiblyPairDownRequest(b, first).build();
-    return message;
+    return b.build();
   }
 
   private ApiStreamObserver<WriteObjectRequest> openedStream() {
@@ -201,48 +187,20 @@ final class GapicUnbufferedDirectWritableByteChannel implements UnbufferedWritab
     return stream;
   }
 
-  /**
-   * Several fields of a WriteObjectRequest are only allowed on the "first" message sent to gcs,
-   * this utility method centralizes the logic necessary to clear those fields for use by subsequent
-   * messages.
-   */
-  private static WriteObjectRequest.Builder possiblyPairDownRequest(
-      WriteObjectRequest.Builder b, boolean firstMessageOfStream) {
-    if (firstMessageOfStream && b.getWriteOffset() == 0) {
-      return b;
-    }
-    if (b.getWriteOffset() > 0) {
-      b.clearWriteObjectSpec();
-    }
-
-    if (b.getWriteOffset() > 0 && !b.getFinishWrite()) {
-      b.clearObjectChecksums();
-    }
-    return b;
-  }
-
-  static class Observer implements ApiStreamObserver<WriteObjectResponse> {
+  class Observer implements ApiStreamObserver<WriteObjectResponse> {
 
-    private final LongConsumer sizeCallback;
-    private final Consumer<WriteObjectResponse> completeCallback;
+    private final GrpcCallContext context;
 
     private final SettableApiFuture<Void> invocationHandle;
     private volatile WriteObjectResponse last;
 
-    Observer(LongConsumer sizeCallback, Consumer<WriteObjectResponse> completeCallback) {
-      this.sizeCallback = sizeCallback;
-      this.completeCallback = completeCallback;
+    Observer(GrpcCallContext context) {
+      this.context = context;
       this.invocationHandle = SettableApiFuture.create();
     }
 
     @Override
     public void onNext(WriteObjectResponse value) {
-      // incremental update
-      if (value.hasPersistedSize()) {
-        sizeCallback.accept(value.getPersistedSize());
-      } else if (value.hasResource()) {
-        sizeCallback.accept(value.getResource().getSize());
-      }
       last = value;
     }
 
@@ -257,15 +215,58 @@ final class GapicUnbufferedDirectWritableByteChannel implements UnbufferedWritab
      */
     @Override
     public void onError(Throwable t) {
-      invocationHandle.setException(t);
+      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(), getRequests(), null, context, t);
+        invocationHandle.setException(storageException);
+      } else {
+        invocationHandle.setException(t);
+      }
     }
 
     @Override
     public void onCompleted() {
-      if (last != null && last.hasResource()) {
-        completeCallback.accept(last);
+      try {
+        if (last == null) {
+          throw new StorageException(
+              0, "onComplete without preceding onNext, unable to determine success.");
+        } else if (last.hasResource()) {
+          long totalSentBytes = writeCtx.getTotalSentBytes().get();
+          long finalSize = last.getResource().getSize();
+          if (totalSentBytes == finalSize) {
+            writeCtx.getConfirmedBytes().set(finalSize);
+            resultFuture.set(last);
+          } else if (finalSize < totalSentBytes) {
+            throw ResumableSessionFailureScenario.SCENARIO_4_1.toStorageException(
+                getRequests(), last, context, null);
+          } else {
+            throw ResumableSessionFailureScenario.SCENARIO_4_2.toStorageException(
+                getRequests(), last, context, null);
+          }
+        } else {
+          throw ResumableSessionFailureScenario.SCENARIO_0.toStorageException(
+              getRequests(), last, context, null);
+        }
+      } catch (Throwable se) {
+        open = false;
+        invocationHandle.setException(se);
+      } finally {
+        invocationHandle.set(null);
+      }
+    }
+
+    private @NonNull ImmutableList<@NonNull WriteObjectRequest> getRequests() {
+      if (lastWrittenRequest == null) {
+        return ImmutableList.of();
+      } else {
+        return ImmutableList.of(lastWrittenRequest);
       }
-      invocationHandle.set(null);
     }
 
     void await() {
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicWritableByteChannelSessionBuilder.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicWritableByteChannelSessionBuilder.java
index 88540533..f4a8afdb 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicWritableByteChannelSessionBuilder.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicWritableByteChannelSessionBuilder.java
@@ -185,7 +185,7 @@ final class GapicWritableByteChannelSessionBuilder {
                         resultFuture,
                         getChunkSegmenter(),
                         write,
-                        WriteObjectRequestBuilderFactory.simple(start)))
+                        new WriteCtx<>(WriteObjectRequestBuilderFactory.simple(start))))
                 .andThen(StorageByteChannels.writable()::createSynchronized));
       }
     }
@@ -213,7 +213,7 @@ final class GapicWritableByteChannelSessionBuilder {
                         resultFuture,
                         getChunkSegmenter(),
                         write,
-                        WriteObjectRequestBuilderFactory.simple(start)))
+                        new WriteCtx<>(WriteObjectRequestBuilderFactory.simple(start))))
                 .andThen(c -> new DefaultBufferedWritableByteChannel(bufferHandle, c))
                 .andThen(StorageByteChannels.writable()::createSynchronized));
       }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadBlobWriteSessionConfig.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadBlobWriteSessionConfig.java
index c37f3e1a..33ff7399 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadBlobWriteSessionConfig.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadBlobWriteSessionConfig.java
@@ -1052,7 +1052,8 @@ public final class ParallelCompositeUploadBlobWriteSessionConfig extends BlobWri
                 storageInternal,
                 info,
                 opts);
-        return ApiFutures.immediateFuture(channel);
+        return ApiFutures.immediateFuture(
+            StorageByteChannels.writable().createSynchronized(channel));
       }
 
       @Override
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java
index a0dce2cf..58e9bad0 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java
@@ -166,7 +166,7 @@ final class ParallelCompositeUploadWritableByteChannel implements BufferedWritab
   }
 
   @Override
-  public synchronized int write(ByteBuffer src) throws IOException {
+  public int write(ByteBuffer src) throws IOException {
     if (!open) {
       throw new ClosedChannelException();
     }
@@ -190,12 +190,12 @@ final class ParallelCompositeUploadWritableByteChannel implements BufferedWritab
   }
 
   @Override
-  public synchronized boolean isOpen() {
+  public boolean isOpen() {
     return open;
   }
 
   @Override
-  public synchronized void flush() throws IOException {
+  public void flush() throws IOException {
     if (current != null) {
       ByteBuffer buf = current.getBufferHandle().get();
       internalFlush(buf);
@@ -203,7 +203,7 @@ final class ParallelCompositeUploadWritableByteChannel implements BufferedWritab
   }
 
   @Override
-  public synchronized void close() throws IOException {
+  public void close() throws IOException {
     if (!open) {
       return;
     }
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 294d481c..8c06d70d 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
@@ -64,11 +64,11 @@ enum ResumableSessionFailureScenario {
   SCENARIO_4_1(
       BaseServiceException.UNKNOWN_CODE,
       "dataLoss",
-      "Finalized resumable session, but object size less than expected."),
+      "Finalized upload, but object size less than expected."),
   SCENARIO_4_2(
       BaseServiceException.UNKNOWN_CODE,
       "dataLoss",
-      "Finalized resumable session, but object size greater than expected."),
+      "Finalized upload, but object size greater than expected."),
   SCENARIO_5(
       BaseServiceException.UNKNOWN_CODE,
       "dataLoss",
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
index 1a860a05..fca1d129 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
@@ -2906,18 +2906,16 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
   Blob createFrom(BlobInfo blobInfo, Path path, BlobWriteOption... options) throws IOException;
 
   /**
-   * Uploads {@code path} to the blob using {@link #writer} and {@code bufferSize}. By default any
-   * MD5 and CRC32C values in the given {@code blobInfo} are ignored unless requested via the {@link
+   * Uploads {@code path} to the blob using {@code ResumableSession}. By default any MD5 and CRC32C
+   * values in the given {@code blobInfo} are ignored unless requested via the {@link
    * BlobWriteOption#md5Match()} and {@link BlobWriteOption#crc32cMatch()} options. Folder upload is
    * not supported. Note that all <a href="https://cloud.google.com/storage/docs/metadata#fixed">
    * non-editable metadata</a>, such as generation or metageneration, will be ignored even if it's
    * present in the provided BlobInfo object.
    *
-   * <p>{@link #createFrom(BlobInfo, Path, BlobWriteOption...)} invokes this method with a buffer
-   * size of 15 MiB. Users can pass alternative values. Larger buffer sizes might improve the upload
-   * performance but require more memory. This can cause an OutOfMemoryError or add significant
-   * garbage collection overhead. Smaller buffer sizes reduce memory consumption, that is noticeable
-   * when uploading many objects in parallel. Buffer sizes less than 256 KiB are treated as 256 KiB.
+   * <p>This method used to preallocate a buffer, but since v2.25.0, it uses a ResumableSession and
+   * no longer needs it. The bufferSize parameter is still present for binary compatibility, but is
+   * now ignored.
    *
    * <p>Example of uploading a humongous file:
    *
@@ -2925,14 +2923,13 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
    * BlobId blobId = BlobId.of(bucketName, blobName);
    * BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("video/webm").build();
    *
-   * int largeBufferSize = 150 * 1024 * 1024;
    * Path file = Paths.get("humongous.file");
-   * storage.createFrom(blobInfo, file, largeBufferSize);
+   * storage.createFrom(blobInfo, file, 0);
    * }</pre>
    *
    * @param blobInfo blob to create
    * @param path file to upload
-   * @param bufferSize size of the buffer I/O operations
+   * @param bufferSize ignored field, still present for compatibility purposes
    * @param options blob write options
    * @return a {@code Blob} with complete information
    * @throws IOException on I/O error
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageByteChannels.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageByteChannels.java
index fb14d2e8..7f02c749 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageByteChannels.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageByteChannels.java
@@ -25,6 +25,7 @@ import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.ScatteringByteChannel;
+import java.util.concurrent.locks.ReentrantLock;
 
 final class StorageByteChannels {
 
@@ -74,24 +75,41 @@ final class StorageByteChannels {
       implements BufferedReadableByteChannel {
 
     private final BufferedReadableByteChannel delegate;
+    private final ReentrantLock lock;
 
     public SynchronizedBufferedReadableByteChannel(BufferedReadableByteChannel delegate) {
       this.delegate = delegate;
+      this.lock = new ReentrantLock();
     }
 
     @Override
-    public synchronized int read(ByteBuffer dst) throws IOException {
-      return delegate.read(dst);
+    public int read(ByteBuffer dst) throws IOException {
+      lock.lock();
+      try {
+        return delegate.read(dst);
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
     public boolean isOpen() {
-      return delegate.isOpen();
+      lock.lock();
+      try {
+        return delegate.isOpen();
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
-    public synchronized void close() throws IOException {
-      delegate.close();
+    public void close() throws IOException {
+      lock.lock();
+      try {
+        delegate.close();
+      } finally {
+        lock.unlock();
+      }
     }
   }
 
@@ -99,29 +117,51 @@ final class StorageByteChannels {
       implements BufferedWritableByteChannel {
 
     private final BufferedWritableByteChannel delegate;
+    private final ReentrantLock lock;
 
     public SynchronizedBufferedWritableByteChannel(BufferedWritableByteChannel delegate) {
       this.delegate = delegate;
+      this.lock = new ReentrantLock();
     }
 
     @Override
-    public synchronized int write(ByteBuffer src) throws IOException {
-      return delegate.write(src);
+    public int write(ByteBuffer src) throws IOException {
+      lock.lock();
+      try {
+        return delegate.write(src);
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
     public boolean isOpen() {
-      return delegate.isOpen();
+      lock.lock();
+      try {
+        return delegate.isOpen();
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
-    public synchronized void close() throws IOException {
-      delegate.close();
+    public void close() throws IOException {
+      lock.lock();
+      try {
+        delegate.close();
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
-    public synchronized void flush() throws IOException {
-      delegate.flush();
+    public void flush() throws IOException {
+      lock.lock();
+      try {
+        delegate.flush();
+      } finally {
+        lock.unlock();
+      }
     }
   }
 
@@ -129,34 +169,61 @@ final class StorageByteChannels {
       implements UnbufferedReadableByteChannel {
 
     private final UnbufferedReadableByteChannel delegate;
+    private final ReentrantLock lock;
 
     private SynchronizedUnbufferedReadableByteChannel(UnbufferedReadableByteChannel delegate) {
       this.delegate = delegate;
+      this.lock = new ReentrantLock();
     }
 
     @Override
-    public synchronized int read(ByteBuffer src) throws IOException {
-      return delegate.read(src);
+    public int read(ByteBuffer src) throws IOException {
+      lock.lock();
+      try {
+        return delegate.read(src);
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
-    public synchronized long read(ByteBuffer[] dsts) throws IOException {
-      return delegate.read(dsts);
+    public long read(ByteBuffer[] dsts) throws IOException {
+      lock.lock();
+      try {
+        return delegate.read(dsts);
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
-    public synchronized long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
-      return delegate.read(dsts, offset, length);
+    public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
+      lock.lock();
+      try {
+        return delegate.read(dsts, offset, length);
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
     public boolean isOpen() {
-      return delegate.isOpen();
+      lock.lock();
+      try {
+        return delegate.isOpen();
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
-    public synchronized void close() throws IOException {
-      delegate.close();
+    public void close() throws IOException {
+      lock.lock();
+      try {
+        delegate.close();
+      } finally {
+        lock.unlock();
+      }
     }
   }
 
@@ -164,50 +231,91 @@ final class StorageByteChannels {
       implements UnbufferedWritableByteChannel {
 
     private final UnbufferedWritableByteChannel delegate;
+    private final ReentrantLock lock;
 
     private SynchronizedUnbufferedWritableByteChannel(UnbufferedWritableByteChannel delegate) {
       this.delegate = delegate;
+      this.lock = new ReentrantLock();
     }
 
     @Override
-    public synchronized int write(ByteBuffer src) throws IOException {
-      return delegate.write(src);
+    public int write(ByteBuffer src) throws IOException {
+      lock.lock();
+      try {
+        return delegate.write(src);
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
-    public synchronized long write(ByteBuffer[] srcs) throws IOException {
-      return delegate.write(srcs);
+    public long write(ByteBuffer[] srcs) throws IOException {
+      lock.lock();
+      try {
+        return delegate.write(srcs);
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
-    public synchronized long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
-      return delegate.write(srcs, offset, length);
+    public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
+      lock.lock();
+      try {
+        return delegate.write(srcs, offset, length);
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
-    public synchronized int writeAndClose(ByteBuffer src) throws IOException {
-      return delegate.writeAndClose(src);
+    public int writeAndClose(ByteBuffer src) throws IOException {
+      lock.lock();
+      try {
+        return delegate.writeAndClose(src);
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
-    public synchronized long writeAndClose(ByteBuffer[] srcs) throws IOException {
-      return delegate.writeAndClose(srcs);
+    public long writeAndClose(ByteBuffer[] srcs) throws IOException {
+      lock.lock();
+      try {
+        return delegate.writeAndClose(srcs);
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
-    public synchronized long writeAndClose(ByteBuffer[] srcs, int offset, int length)
-        throws IOException {
-      return delegate.writeAndClose(srcs, offset, length);
+    public long writeAndClose(ByteBuffer[] srcs, int offset, int length) throws IOException {
+      lock.lock();
+      try {
+        return delegate.writeAndClose(srcs, offset, length);
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
     public boolean isOpen() {
-      return delegate.isOpen();
+      lock.lock();
+      try {
+        return delegate.isOpen();
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
-    public synchronized void close() throws IOException {
-      delegate.close();
+    public void close() throws IOException {
+      lock.lock();
+      try {
+        delegate.close();
+      } finally {
+        lock.unlock();
+      }
     }
   }
 
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ThroughputSink.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ThroughputSink.java
index bf9d523b..7d6d909d 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ThroughputSink.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ThroughputSink.java
@@ -27,6 +27,7 @@ import java.time.Duration;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.Objects;
+import java.util.concurrent.locks.ReentrantLock;
 import java.util.logging.Logger;
 
 /**
@@ -348,15 +349,22 @@ interface ThroughputSink {
   final class ThroughputMovingWindowThroughputSink implements ThroughputSink {
     private final ThroughputMovingWindow w;
     private final Clock clock;
+    private final ReentrantLock lock;
 
     private ThroughputMovingWindowThroughputSink(ThroughputMovingWindow w, Clock clock) {
       this.w = w;
       this.clock = clock;
+      this.lock = new ReentrantLock();
     }
 
     @Override
-    public synchronized void recordThroughput(Record r) {
-      w.add(r.end, Throughput.of(r.getNumBytes(), r.getDuration()));
+    public void recordThroughput(Record r) {
+      lock.lock();
+      try {
+        w.add(r.end, Throughput.of(r.getNumBytes(), r.getDuration()));
+      } finally {
+        lock.unlock();
+      }
     }
 
     @Override
diff --git a/grpc-google-cloud-storage-control-v2/pom.xml b/grpc-google-cloud-storage-control-v2/pom.xml
index dd513cc3..248d2d8b 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.39.0-beta</version><!-- {x-version-update:grpc-google-cloud-storage-control-v2:current} -->
+  <version>2.40.0</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.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+    <version>2.40.0</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 4f9b97ee..44bd9d5a 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.39.0-alpha</version><!-- {x-version-update:grpc-google-cloud-storage-v2:current} -->
+  <version>2.40.0-alpha</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.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+    <version>2.40.0</version><!-- {x-version-update:google-cloud-storage:current} -->
   </parent>
   <dependencies>
     <dependency>
diff --git a/pom.xml b/pom.xml
index 10bb5f3f..306f89a6 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.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+  <version>2.40.0</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.30.1</version>
+    <version>3.31.0</version>
   </parent>
 
   <developers>
@@ -76,7 +76,7 @@
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>google-cloud-storage</artifactId>
-        <version>2.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+        <version>2.40.0</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.5</version>
+        <version>1.130.0</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.39.0-alpha</version><!-- {x-version-update:proto-google-cloud-storage-v2:current} -->
+        <version>2.40.0-alpha</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.39.0-alpha</version><!-- {x-version-update:grpc-google-cloud-storage-v2:current} -->
+        <version>2.40.0-alpha</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.39.0-alpha</version><!-- {x-version-update:grpc-google-cloud-storage-v2:current} -->
+        <version>2.40.0-alpha</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.39.0-beta</version><!-- {x-version-update:grpc-google-cloud-storage-control-v2:current} -->
+        <version>2.40.0</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.39.0-beta</version><!-- {x-version-update:proto-google-cloud-storage-control-v2:current} -->
+        <version>2.40.0</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.39.0-beta</version><!-- {x-version-update:google-cloud-storage-control:current} -->
+        <version>2.40.0</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 fa454bb1..c677dff1 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.39.0-beta</version><!-- {x-version-update:proto-google-cloud-storage-control-v2:current} -->
+  <version>2.40.0</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.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+    <version>2.40.0</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 dfc85cdc..3a7df5c8 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.39.0-alpha</version><!-- {x-version-update:proto-google-cloud-storage-v2:current} -->
+  <version>2.40.0-alpha</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.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+    <version>2.40.0</version><!-- {x-version-update:google-cloud-storage:current} -->
   </parent>
   <dependencies>
     <dependency>
diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml
index d4893b17..6fd6fa51 100644
--- a/samples/install-without-bom/pom.xml
+++ b/samples/install-without-bom/pom.xml
@@ -30,12 +30,12 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-storage</artifactId>
-      <version>2.38.0</version>
+      <version>2.39.0</version>
     </dependency>
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-storage-control</artifactId>
-      <version>2.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+      <version>2.40.0</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.5</version>
+      <version>1.130.0</version>
       <scope>test</scope>
     </dependency>
     <dependency>
diff --git a/samples/native-image-sample/pom.xml b/samples/native-image-sample/pom.xml
index 9229c8e1..ef08c54f 100644
--- a/samples/native-image-sample/pom.xml
+++ b/samples/native-image-sample/pom.xml
@@ -29,7 +29,7 @@
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>libraries-bom</artifactId>
-        <version>26.39.0</version>
+        <version>26.40.0</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
@@ -61,13 +61,13 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-pubsub</artifactId>
-      <version>1.129.5</version>
+      <version>1.130.0</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-storage-control</artifactId>
-      <version>2.39.0-beta</version><!-- {x-version-update:google-cloud-storage-control:current} -->
+      <version>2.40.0</version><!-- {x-version-update:google-cloud-storage-control:current} -->
       <scope>compile</scope>
     </dependency>
   </dependencies>
diff --git a/samples/pom.xml b/samples/pom.xml
index c1b0d76c..7c252467 100644
--- a/samples/pom.xml
+++ b/samples/pom.xml
@@ -48,7 +48,7 @@
       <plugin>
         <groupId>org.sonatype.plugins</groupId>
         <artifactId>nexus-staging-maven-plugin</artifactId>
-        <version>1.6.13</version>
+        <version>1.7.0</version>
         <configuration>
           <skipNexusStagingDeployMojo>true</skipNexusStagingDeployMojo>
         </configuration>
diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml
index 2012436e..65f639d6 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.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+      <version>2.40.0</version><!-- {x-version-update:google-cloud-storage:current} -->
     </dependency>
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-storage-control</artifactId>
-      <version>2.39.0-beta</version><!-- {x-version-update:google-cloud-storage-control:current} -->
+      <version>2.40.0</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.5</version>
+      <version>1.130.0</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index 7be5d0f9..8941aebf 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -31,7 +31,7 @@
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>libraries-bom</artifactId>
-        <version>26.39.0</version>
+        <version>26.40.0</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
@@ -76,7 +76,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-pubsub</artifactId>
-      <version>1.129.5</version>
+      <version>1.130.0</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/samples/snippets/src/main/java/com/example/storage/managedfolders/CreateManagedFolder.java b/samples/snippets/src/main/java/com/example/storage/managedfolders/CreateManagedFolder.java
new file mode 100644
index 00000000..23744ed6
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/storage/managedfolders/CreateManagedFolder.java
@@ -0,0 +1,44 @@
+/*
+ * 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.example.storage.managedfolders;
+
+// [START storage_control_managed_folder_create]
+
+import com.google.storage.control.v2.BucketName;
+import com.google.storage.control.v2.CreateManagedFolderRequest;
+import com.google.storage.control.v2.ManagedFolder;
+import com.google.storage.control.v2.StorageControlClient;
+
+public class CreateManagedFolder {
+  public static void managedFolderCreate(String bucketName, String managedFolderId)
+      throws Exception {
+
+    // Instantiates a client in a try-with-resource to automatically cleanup underlying resources
+    try (StorageControlClient storageControlClient = StorageControlClient.create()) {
+      CreateManagedFolderRequest request =
+          CreateManagedFolderRequest.newBuilder()
+              // Set project to "_" to signify global bucket
+              .setParent(BucketName.format("_", bucketName))
+              .setManagedFolder(ManagedFolder.newBuilder().build())
+              .setManagedFolderId(managedFolderId)
+              .build();
+      String response = storageControlClient.createManagedFolder(request).getName();
+      System.out.printf("Performed createManagedFolder request for %s%n", response);
+    }
+  }
+}
+// [END storage_control_managed_folder_create]
diff --git a/samples/snippets/src/main/java/com/example/storage/managedfolders/DeleteManagedFolder.java b/samples/snippets/src/main/java/com/example/storage/managedfolders/DeleteManagedFolder.java
new file mode 100644
index 00000000..b72640c1
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/storage/managedfolders/DeleteManagedFolder.java
@@ -0,0 +1,46 @@
+/*
+ * 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.example.storage.managedfolders;
+
+// [START storage_control_managed_folder_delete]
+import com.google.storage.control.v2.BucketName;
+import com.google.storage.control.v2.DeleteManagedFolderRequest;
+import com.google.storage.control.v2.ManagedFolderName;
+import com.google.storage.control.v2.StorageControlClient;
+
+class DeleteManagedFolder {
+  public static void managedFolderDelete(String bucketName, String managedFolderId)
+      throws Exception {
+    // Instantiates a client in a try-with-resource to automatically cleanup underlying resources
+    try (StorageControlClient storageControlClient = StorageControlClient.create()) {
+      // Set project to "_" to signify global bucket
+      BucketName resourceBucketName = BucketName.of("_", bucketName);
+      DeleteManagedFolderRequest deleteManagedFolderRequest =
+          DeleteManagedFolderRequest.newBuilder()
+              .setName(
+                  ManagedFolderName.format(
+                      resourceBucketName.getProject(),
+                      resourceBucketName.getBucket(),
+                      managedFolderId))
+              .build();
+      storageControlClient.deleteManagedFolder(deleteManagedFolderRequest);
+      System.out.printf("Deleted Managed Folder %s%n", managedFolderId);
+    }
+  }
+}
+
+// [END storage_control_managed_folder_delete]
diff --git a/samples/snippets/src/main/java/com/example/storage/managedfolders/GetManagedFolder.java b/samples/snippets/src/main/java/com/example/storage/managedfolders/GetManagedFolder.java
new file mode 100644
index 00000000..683444c1
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/storage/managedfolders/GetManagedFolder.java
@@ -0,0 +1,48 @@
+/*
+ * 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.example.storage.managedfolders;
+
+// [START storage_control_managed_folder_get]
+
+import com.google.storage.control.v2.BucketName;
+import com.google.storage.control.v2.GetManagedFolderRequest;
+import com.google.storage.control.v2.ManagedFolder;
+import com.google.storage.control.v2.ManagedFolderName;
+import com.google.storage.control.v2.StorageControlClient;
+
+class GetManagedFolder {
+
+  public static void managedFolderGet(String bucketName, String managedFolderId) throws Exception {
+    // Instantiates a client in a try-with-resource to automatically cleanup underlying resources
+    try (StorageControlClient storageControlClient = StorageControlClient.create()) {
+      // Set project to "_" to signify global bucket
+      BucketName resourceBucketName = BucketName.of("_", bucketName);
+      GetManagedFolderRequest getManagedFolderRequest =
+          GetManagedFolderRequest.newBuilder()
+              .setName(
+                  ManagedFolderName.format(
+                      resourceBucketName.getProject(),
+                      resourceBucketName.getBucket(),
+                      managedFolderId))
+              .build();
+      ManagedFolder managedFolder = storageControlClient.getManagedFolder(getManagedFolderRequest);
+      System.out.printf("Got Managed Folder %s%n", managedFolder.getName());
+    }
+  }
+}
+
+// [END storage_control_managed_folder_get]
diff --git a/samples/snippets/src/main/java/com/example/storage/managedfolders/ListManagedFolders.java b/samples/snippets/src/main/java/com/example/storage/managedfolders/ListManagedFolders.java
new file mode 100644
index 00000000..bb7ce977
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/storage/managedfolders/ListManagedFolders.java
@@ -0,0 +1,45 @@
+/*
+ * 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.example.storage.managedfolders;
+
+// [START storage_control_managed_folder_list]
+
+import com.google.storage.control.v2.BucketName;
+import com.google.storage.control.v2.ListManagedFoldersRequest;
+import com.google.storage.control.v2.ManagedFolder;
+import com.google.storage.control.v2.StorageControlClient;
+
+class ListManagedFolders {
+
+  public static void managedFolderList(String bucketName) throws Exception {
+    // Instantiates a client in a try-with-resource to automatically cleanup underlying resources
+    try (StorageControlClient storageControlClient = StorageControlClient.create()) {
+      ListManagedFoldersRequest listManagedFoldersRequest =
+          ListManagedFoldersRequest.newBuilder()
+              // Set project to "_" to signify global bucket
+              .setParent(BucketName.format("_", bucketName))
+              .build();
+      Iterable<ManagedFolder> managedFolders =
+          storageControlClient.listManagedFolders(listManagedFoldersRequest).iterateAll();
+      for (ManagedFolder folder : managedFolders) {
+        System.out.printf("%s bucket has managed folder %s%n", bucketName, folder.getName());
+      }
+    }
+  }
+}
+
+// [END storage_control_managed_folder_list]
diff --git a/storage-shared-benchmarking/pom.xml b/storage-shared-benchmarking/pom.xml
index ba49f60b..98c9ff31 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.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+    <version>2.40.0</version><!-- {x-version-update:google-cloud-storage:current} -->
   </parent>
 
   <properties>
@@ -31,7 +31,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-storage</artifactId>
-      <version>2.39.0</version><!-- {x-version-update:google-cloud-storage:current} -->
+      <version>2.40.0</version><!-- {x-version-update:google-cloud-storage:current} -->
       <classifier>tests</classifier>
     </dependency>
     <dependency>
diff --git a/versions.txt b/versions.txt
index 46958f51..b491c733 100644
--- a/versions.txt
+++ b/versions.txt
@@ -1,10 +1,10 @@
 # Format:
 # module:released-version:current-version
 
-google-cloud-storage:2.39.0:2.39.0
-gapic-google-cloud-storage-v2:2.39.0-alpha:2.39.0-alpha
-grpc-google-cloud-storage-v2:2.39.0-alpha:2.39.0-alpha
-proto-google-cloud-storage-v2:2.39.0-alpha:2.39.0-alpha
-google-cloud-storage-control:2.39.0-beta:2.39.0-beta
-proto-google-cloud-storage-control-v2:2.39.0-beta:2.39.0-beta
-grpc-google-cloud-storage-control-v2:2.39.0-beta:2.39.0-beta
+google-cloud-storage:2.40.0:2.40.0
+gapic-google-cloud-storage-v2:2.40.0-alpha:2.40.0-alpha
+grpc-google-cloud-storage-v2:2.40.0-alpha:2.40.0-alpha
+proto-google-cloud-storage-v2:2.40.0-alpha:2.40.0-alpha
+google-cloud-storage-control:2.40.0:2.40.0
+proto-google-cloud-storage-control-v2:2.40.0:2.40.0
+grpc-google-cloud-storage-control-v2:2.40.0:2.40.0


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedDirectWritableByteChannelTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedDirectWritableByteChannelTest.java
new file mode 100644
index 00000000..4a70e9a2
--- /dev/null
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedDirectWritableByteChannelTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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;
+
+import static com.google.cloud.storage.ByteSizeConstants._256KiB;
+import static com.google.cloud.storage.ByteSizeConstants._512KiB;
+import static com.google.cloud.storage.ByteSizeConstants._768KiB;
+import static com.google.cloud.storage.TestUtils.assertAll;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.api.core.SettableApiFuture;
+import com.google.cloud.storage.ITGapicUnbufferedWritableByteChannelTest.DirectWriteService;
+import com.google.cloud.storage.WriteCtx.SimpleWriteObjectRequestBuilderFactory;
+import com.google.cloud.storage.WriteCtx.WriteObjectRequestBuilderFactory;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.storage.v2.Object;
+import com.google.storage.v2.StorageClient;
+import com.google.storage.v2.WriteObjectRequest;
+import com.google.storage.v2.WriteObjectResponse;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+
+public final class ITGapicUnbufferedDirectWritableByteChannelTest {
+
+  private static final ChunkSegmenter CHUNK_SEGMENTER =
+      new ChunkSegmenter(Hasher.noop(), ByteStringStrategy.copy(), _256KiB, _256KiB);
+
+  /** Attempting to finalize, ack equals expected */
+  @Test
+  public void ack_eq() throws Exception {
+    WriteObjectRequest req1 =
+        WriteObjectRequest.newBuilder().setWriteOffset(_256KiB).setFinishWrite(true).build();
+    WriteObjectResponse resp1 =
+        WriteObjectResponse.newBuilder()
+            .setResource(Object.newBuilder().setName("name").setSize(_256KiB).build())
+            .build();
+
+    ImmutableMap<List<WriteObjectRequest>, WriteObjectResponse> map =
+        ImmutableMap.of(ImmutableList.of(req1), resp1);
+    DirectWriteService service1 = new DirectWriteService(map);
+
+    try (FakeServer fakeServer = FakeServer.of(service1);
+        GrpcStorageImpl storage =
+            (GrpcStorageImpl) fakeServer.getGrpcStorageOptions().getService()) {
+      StorageClient storageClient = storage.storageClient;
+
+      SettableApiFuture<WriteObjectResponse> done = SettableApiFuture.create();
+      WriteCtx<SimpleWriteObjectRequestBuilderFactory> writeCtx =
+          new WriteCtx<>(WriteObjectRequestBuilderFactory.simple(req1));
+      writeCtx.getTotalSentBytes().set(_256KiB);
+      writeCtx.getConfirmedBytes().set(0);
+
+      GapicUnbufferedDirectWritableByteChannel channel =
+          new GapicUnbufferedDirectWritableByteChannel(
+              done, CHUNK_SEGMENTER, storageClient.writeObjectCallable(), writeCtx);
+
+      channel.close();
+
+      WriteObjectResponse writeObjectResponse = done.get(2, TimeUnit.SECONDS);
+      assertAll(
+          () -> assertThat(writeObjectResponse).isEqualTo(resp1),
+          () -> assertThat(writeCtx.getConfirmedBytes().get()).isEqualTo(_256KiB),
+          () -> assertThat(channel.isOpen()).isFalse());
+    }
+  }
+
+  /** Attempting to finalize, ack &lt; expected */
+  @Test
+  public void ack_lt() throws Exception {
+    WriteObjectRequest req1 =
+        WriteObjectRequest.newBuilder().setWriteOffset(_512KiB).setFinishWrite(true).build();
+    WriteObjectResponse resp1 =
+        WriteObjectResponse.newBuilder()
+            .setResource(Object.newBuilder().setName("name").setSize(_256KiB).build())
+            .build();
+
+    ImmutableMap<List<WriteObjectRequest>, WriteObjectResponse> map =
+        ImmutableMap.of(ImmutableList.of(req1), resp1);
+    DirectWriteService service1 = new DirectWriteService(map);
+
+    try (FakeServer fakeServer = FakeServer.of(service1);
+        GrpcStorageImpl storage =
+            (GrpcStorageImpl) fakeServer.getGrpcStorageOptions().getService()) {
+      StorageClient storageClient = storage.storageClient;
+
+      SettableApiFuture<WriteObjectResponse> done = SettableApiFuture.create();
+      WriteCtx<SimpleWriteObjectRequestBuilderFactory> writeCtx =
+          new WriteCtx<>(WriteObjectRequestBuilderFactory.simple(req1));
+      writeCtx.getTotalSentBytes().set(_512KiB);
+      writeCtx.getConfirmedBytes().set(0);
+
+      //noinspection resource
+      GapicUnbufferedDirectWritableByteChannel channel =
+          new GapicUnbufferedDirectWritableByteChannel(
+              done, CHUNK_SEGMENTER, storageClient.writeObjectCallable(), writeCtx);
+
+      StorageException se = assertThrows(StorageException.class, channel::close);
+      assertAll(
+          () -> assertThat(se.getCode()).isEqualTo(0),
+          () -> assertThat(se.getReason()).isEqualTo("dataLoss"),
+          () -> assertThat(writeCtx.getConfirmedBytes().get()).isEqualTo(0),
+          () -> assertThat(channel.isOpen()).isFalse());
+    }
+  }
+
+  /** Attempting to finalize, ack &gt; expected */
+  @Test
+  public void ack_gt() throws Exception {
+    WriteObjectRequest req1 =
+        WriteObjectRequest.newBuilder().setWriteOffset(_512KiB).setFinishWrite(true).build();
+    WriteObjectResponse resp1 =
+        WriteObjectResponse.newBuilder()
+            .setResource(Object.newBuilder().setName("name").setSize(_768KiB).build())
+            .build();
+
+    ImmutableMap<List<WriteObjectRequest>, WriteObjectResponse> map =
+        ImmutableMap.of(ImmutableList.of(req1), resp1);
+    DirectWriteService service1 = new DirectWriteService(map);
+
+    try (FakeServer fakeServer = FakeServer.of(service1);
+        GrpcStorageImpl storage =
+            (GrpcStorageImpl) fakeServer.getGrpcStorageOptions().getService()) {
+      StorageClient storageClient = storage.storageClient;
+
+      SettableApiFuture<WriteObjectResponse> done = SettableApiFuture.create();
+      WriteCtx<SimpleWriteObjectRequestBuilderFactory> writeCtx =
+          new WriteCtx<>(WriteObjectRequestBuilderFactory.simple(req1));
+      writeCtx.getTotalSentBytes().set(_512KiB);
+      writeCtx.getConfirmedBytes().set(0);
+
+      //noinspection resource
+      GapicUnbufferedDirectWritableByteChannel channel =
+          new GapicUnbufferedDirectWritableByteChannel(
+              done, CHUNK_SEGMENTER, storageClient.writeObjectCallable(), writeCtx);
+
+      StorageException se = assertThrows(StorageException.class, channel::close);
+      assertAll(
+          () -> assertThat(se.getCode()).isEqualTo(0),
+          () -> assertThat(se.getReason()).isEqualTo("dataLoss"),
+          () -> assertThat(writeCtx.getConfirmedBytes().get()).isEqualTo(0),
+          () -> assertThat(channel.isOpen()).isFalse());
+    }
+  }
+}
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedWritableByteChannelTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedWritableByteChannelTest.java
index 239b9bf3..326cbb15 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedWritableByteChannelTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedWritableByteChannelTest.java
@@ -153,11 +153,14 @@ public final class ITGapicUnbufferedWritableByteChannelTest {
         new DirectWriteService(
             ImmutableMap.of(ImmutableList.of(req1, req2, req3, req4, req5), resp));
     try (FakeServer fake = FakeServer.of(service);
-        StorageClient sc = StorageClient.create(fake.storageSettings())) {
+        StorageClient sc =
+            PackagePrivateMethodWorkarounds.maybeGetStorageClient(
+                fake.getGrpcStorageOptions().getService())) {
+      assertThat(sc).isNotNull();
       SettableApiFuture<WriteObjectResponse> result = SettableApiFuture.create();
       try (GapicUnbufferedDirectWritableByteChannel c =
           new GapicUnbufferedDirectWritableByteChannel(
-              result, segmenter, sc.writeObjectCallable(), reqFactory)) {
+              result, segmenter, sc.writeObjectCallable(), new WriteCtx<>(reqFactory))) {
         c.write(ByteBuffer.wrap(bytes));
       }
       assertThat(result.get()).isEqualTo(resp);
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 8aab9ba6..4ed6887f 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
@@ -23,6 +23,8 @@ import static com.google.cloud.storage.it.runner.registry.RegistryApplicabilityP
 
 import com.google.cloud.NoCredentials;
 import com.google.cloud.storage.BucketInfo;
+import com.google.cloud.storage.GrpcStorageOptions;
+import com.google.cloud.storage.HttpStorageOptions;
 import com.google.cloud.storage.Storage;
 import com.google.cloud.storage.StorageOptions;
 import com.google.cloud.storage.TransportCompatibility.Transport;
@@ -43,36 +45,10 @@ final class BackendResources implements ManagedLifecycle {
   private BackendResources(
       Backend backend,
       ProtectedBucketNames protectedBucketNames,
-      TestRunScopedInstance<StorageInstance> storageJson,
-      TestRunScopedInstance<StorageInstance> storageGrpc,
-      TestRunScopedInstance<BucketInfoShim> bucket,
-      TestRunScopedInstance<BucketInfoShim> bucketRequesterPays,
-      TestRunScopedInstance<ObjectsFixture> objectsFixture,
-      TestRunScopedInstance<ObjectsFixture> objectsFixtureRequesterPays,
-      TestRunScopedInstance<KmsFixture> kmsFixture) {
+      ImmutableList<RegistryEntry<?>> registryEntries) {
     this.backend = backend;
     this.protectedBucketNames = protectedBucketNames;
-    this.registryEntries =
-        ImmutableList.of(
-            RegistryEntry.of(
-                4, Storage.class, storageJson, transportAndBackendAre(Transport.HTTP, backend)),
-            RegistryEntry.of(
-                5, Storage.class, storageGrpc, transportAndBackendAre(Transport.GRPC, backend)),
-            RegistryEntry.of(
-                6,
-                BucketInfo.class,
-                bucketRequesterPays,
-                backendIs(backend).and(isRequesterPaysBucket())),
-            RegistryEntry.of(
-                7, BucketInfo.class, bucket, backendIs(backend).and(isDefaultBucket())),
-            RegistryEntry.of(
-                8, ObjectsFixture.class, objectsFixture, backendIs(backend).and(isDefaultBucket())),
-            RegistryEntry.of(
-                9,
-                ObjectsFixture.class,
-                objectsFixtureRequesterPays,
-                backendIs(backend).and(isRequesterPaysBucket())),
-            RegistryEntry.of(10, KmsFixture.class, kmsFixture, backendIs(backend)));
+    this.registryEntries = registryEntries;
   }
 
   public ImmutableList<RegistryEntry<?>> getRegistryEntries() {
@@ -97,41 +73,53 @@ final class BackendResources implements ManagedLifecycle {
     return MoreObjects.toStringHelper(this).add("backend", backend).toString();
   }
 
+  @SuppressWarnings("SwitchStatementWithTooFewBranches")
   static BackendResources of(Backend backend) {
     ProtectedBucketNames protectedBucketNames = new ProtectedBucketNames();
     TestRunScopedInstance<StorageInstance> storageJson =
         TestRunScopedInstance.of(
             "STORAGE_JSON_" + backend.name(),
-            () ->
-                new StorageInstance(
-                    backend == Backend.TEST_BENCH
-                        ? StorageOptions.http()
-                            .setCredentials(NoCredentials.getInstance())
-                            // TODO: improve this
-                            .setHost(Registry.getInstance().testBench().getBaseUri())
-                            .setProjectId("test-project-id")
-                            .build()
-                        : StorageOptions.http().build(),
-                    protectedBucketNames));
+            () -> {
+              HttpStorageOptions.Builder optionsBuilder;
+              switch (backend) {
+                case TEST_BENCH:
+                  optionsBuilder =
+                      StorageOptions.http()
+                          .setCredentials(NoCredentials.getInstance())
+                          .setHost(Registry.getInstance().testBench().getBaseUri())
+                          .setProjectId("test-project-id");
+                  break;
+                default: // PROD, java8 doesn't have exhaustive checking for enum switch
+                  optionsBuilder = StorageOptions.http();
+                  break;
+              }
+              HttpStorageOptions built = optionsBuilder.build();
+              return new StorageInstance(built, protectedBucketNames);
+            });
     TestRunScopedInstance<StorageInstance> storageGrpc =
         TestRunScopedInstance.of(
             "STORAGE_GRPC_" + backend.name(),
-            () ->
-                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()
-                            .setGrpcInterceptorProvider(
-                                GrpcPlainRequestLoggingInterceptor.getInterceptorProvider())
-                            .build(),
-                    protectedBucketNames));
+            () -> {
+              GrpcStorageOptions.Builder optionsBuilder;
+              switch (backend) {
+                case TEST_BENCH:
+                  optionsBuilder =
+                      StorageOptions.grpc()
+                          .setCredentials(NoCredentials.getInstance())
+                          .setHost(Registry.getInstance().testBench().getGRPCBaseUri())
+                          .setProjectId("test-project-id");
+                  break;
+                default: // PROD, java8 doesn't have exhaustive checking for enum switch
+                  optionsBuilder = StorageOptions.grpc();
+                  break;
+              }
+              GrpcStorageOptions built =
+                  optionsBuilder
+                      .setGrpcInterceptorProvider(
+                          GrpcPlainRequestLoggingInterceptor.getInterceptorProvider())
+                      .build();
+              return new StorageInstance(built, protectedBucketNames);
+            });
     TestRunScopedInstance<BucketInfoShim> bucket =
         TestRunScopedInstance.of(
             "BUCKET_" + backend.name(),
@@ -154,7 +142,7 @@ final class BackendResources implements ManagedLifecycle {
         TestRunScopedInstance.of(
             "OBJECTS_FIXTURE_" + backend.name(),
             () -> new ObjectsFixture(storageJson.get().getStorage(), bucket.get().getBucketInfo()));
-    TestRunScopedInstance<ObjectsFixture> objectsFixtureRequesterPays =
+    TestRunScopedInstance<ObjectsFixture> objectsFixtureRp =
         TestRunScopedInstance.of(
             "OBJECTS_FIXTURE_REQUESTER_PAYS_" + backend.name(),
             () ->
@@ -166,12 +154,25 @@ final class BackendResources implements ManagedLifecycle {
     return new BackendResources(
         backend,
         protectedBucketNames,
-        storageJson,
-        storageGrpc,
-        bucket,
-        bucketRp,
-        objectsFixture,
-        objectsFixtureRequesterPays,
-        kmsFixture);
+        ImmutableList.of(
+            RegistryEntry.of(
+                40, Storage.class, storageJson, transportAndBackendAre(Transport.HTTP, backend)),
+            RegistryEntry.of(
+                50, Storage.class, storageGrpc, transportAndBackendAre(Transport.GRPC, backend)),
+            RegistryEntry.of(
+                60, BucketInfo.class, bucketRp, backendIs(backend).and(isRequesterPaysBucket())),
+            RegistryEntry.of(
+                70, BucketInfo.class, bucket, backendIs(backend).and(isDefaultBucket())),
+            RegistryEntry.of(
+                80,
+                ObjectsFixture.class,
+                objectsFixture,
+                backendIs(backend).and(isDefaultBucket())),
+            RegistryEntry.of(
+                90,
+                ObjectsFixture.class,
+                objectsFixtureRp,
+                backendIs(backend).and(isRequesterPaysBucket())),
+            RegistryEntry.of(100, KmsFixture.class, kmsFixture, backendIs(backend))));
   }
 }
diff --git a/samples/snippets/src/test/java/com/example/storage/managedfolders/CreateManagedFolderTest.java b/samples/snippets/src/test/java/com/example/storage/managedfolders/CreateManagedFolderTest.java
new file mode 100644
index 00000000..7bb8b74d
--- /dev/null
+++ b/samples/snippets/src/test/java/com/example/storage/managedfolders/CreateManagedFolderTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.example.storage.managedfolders;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.cloud.storage.Bucket;
+import com.google.cloud.storage.BucketInfo;
+import com.google.cloud.storage.BucketInfo.IamConfiguration;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import com.google.cloud.storage.testing.RemoteStorageHelper;
+import com.google.cloud.testing.junit4.StdOutCaptureRule;
+import com.google.storage.control.v2.DeleteManagedFolderRequest;
+import com.google.storage.control.v2.ManagedFolderName;
+import com.google.storage.control.v2.StorageControlClient;
+import java.io.IOException;
+import java.util.UUID;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class CreateManagedFolderTest {
+
+  @Rule public StdOutCaptureRule stdOut = new StdOutCaptureRule();
+
+  protected String bucketName;
+  protected Storage storage;
+  protected Bucket bucket;
+  protected String managedFolderId;
+  protected StorageControlClient storageControl;
+
+  @Before
+  public void setUp() throws IOException {
+    bucketName = RemoteStorageHelper.generateBucketName();
+    storageControl = StorageControlClient.create();
+    storage = StorageOptions.getDefaultInstance().getService();
+    managedFolderId = "new-managed-folder-" + UUID.randomUUID();
+    BucketInfo bucketInfo =
+        BucketInfo.newBuilder(bucketName)
+            .setIamConfiguration(
+                IamConfiguration.newBuilder().setIsUniformBucketLevelAccessEnabled(true).build())
+            .build();
+    bucket = storage.create(bucketInfo);
+  }
+
+  @After
+  public void tearDown() {
+    storageControl.deleteManagedFolder(
+        DeleteManagedFolderRequest.newBuilder()
+            .setName(ManagedFolderName.format("_", bucketName, managedFolderId))
+            .build());
+    storage.delete(bucketName);
+    storageControl.shutdown();
+  }
+
+  @Test
+  public void testCreateManagedFolder() throws Exception {
+    CreateManagedFolder.managedFolderCreate(bucketName, managedFolderId);
+    String got = stdOut.getCapturedOutputAsUtf8String();
+    assertThat(got).contains(String.format(managedFolderId));
+  }
+}
diff --git a/samples/snippets/src/test/java/com/example/storage/managedfolders/DeleteManagedFolderTest.java b/samples/snippets/src/test/java/com/example/storage/managedfolders/DeleteManagedFolderTest.java
new file mode 100644
index 00000000..7225d95f
--- /dev/null
+++ b/samples/snippets/src/test/java/com/example/storage/managedfolders/DeleteManagedFolderTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.example.storage.managedfolders;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.cloud.storage.Bucket;
+import com.google.cloud.storage.BucketInfo;
+import com.google.cloud.storage.BucketInfo.IamConfiguration;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import com.google.cloud.storage.testing.RemoteStorageHelper;
+import com.google.cloud.testing.junit4.StdOutCaptureRule;
+import com.google.storage.control.v2.BucketName;
+import com.google.storage.control.v2.CreateManagedFolderRequest;
+import com.google.storage.control.v2.ManagedFolder;
+import com.google.storage.control.v2.StorageControlClient;
+import java.io.IOException;
+import java.util.UUID;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class DeleteManagedFolderTest {
+
+  @Rule public StdOutCaptureRule stdOut = new StdOutCaptureRule();
+
+  protected String bucketName;
+  protected Storage storage;
+  protected Bucket bucket;
+  protected String managedFolderId;
+  protected StorageControlClient storageControl;
+
+  @Before
+  public void setUp() throws IOException {
+    bucketName = RemoteStorageHelper.generateBucketName();
+    storageControl = StorageControlClient.create();
+    storage = StorageOptions.getDefaultInstance().getService();
+    managedFolderId = "new-managed-folder-" + UUID.randomUUID();
+    BucketInfo bucketInfo =
+        BucketInfo.newBuilder(bucketName)
+            .setIamConfiguration(
+                IamConfiguration.newBuilder().setIsUniformBucketLevelAccessEnabled(true).build())
+            .build();
+    bucket = storage.create(bucketInfo);
+    storageControl.createManagedFolder(
+        CreateManagedFolderRequest.newBuilder()
+            // Set project to "_" to signify global bucket
+            .setParent(BucketName.format("_", bucketName))
+            .setManagedFolder(ManagedFolder.newBuilder().build())
+            .setManagedFolderId(managedFolderId)
+            .build());
+  }
+
+  @After
+  public void tearDown() {
+    storage.delete(bucketName);
+    storageControl.shutdown();
+  }
+
+  @Test
+  public void testDeleteManagedFolder() throws Exception {
+    DeleteManagedFolder.managedFolderDelete(bucketName, managedFolderId);
+    String got = stdOut.getCapturedOutputAsUtf8String();
+    assertThat(got).contains(String.format(managedFolderId));
+  }
+}
diff --git a/samples/snippets/src/test/java/com/example/storage/managedfolders/GetManagedFolderTest.java b/samples/snippets/src/test/java/com/example/storage/managedfolders/GetManagedFolderTest.java
new file mode 100644
index 00000000..3b5128cf
--- /dev/null
+++ b/samples/snippets/src/test/java/com/example/storage/managedfolders/GetManagedFolderTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.example.storage.managedfolders;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.cloud.storage.Bucket;
+import com.google.cloud.storage.BucketInfo;
+import com.google.cloud.storage.BucketInfo.IamConfiguration;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import com.google.cloud.storage.testing.RemoteStorageHelper;
+import com.google.cloud.testing.junit4.StdOutCaptureRule;
+import com.google.storage.control.v2.BucketName;
+import com.google.storage.control.v2.CreateManagedFolderRequest;
+import com.google.storage.control.v2.DeleteManagedFolderRequest;
+import com.google.storage.control.v2.ManagedFolder;
+import com.google.storage.control.v2.ManagedFolderName;
+import com.google.storage.control.v2.StorageControlClient;
+import java.io.IOException;
+import java.util.UUID;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class GetManagedFolderTest {
+
+  @Rule public StdOutCaptureRule stdOut = new StdOutCaptureRule();
+
+  protected String bucketName;
+  protected Storage storage;
+  protected Bucket bucket;
+  protected String managedFolderId;
+  protected StorageControlClient storageControl;
+
+  @Before
+  public void setUp() throws IOException {
+    bucketName = RemoteStorageHelper.generateBucketName();
+    storageControl = StorageControlClient.create();
+    storage = StorageOptions.getDefaultInstance().getService();
+    managedFolderId = "new-managed-folder-" + UUID.randomUUID();
+    BucketInfo bucketInfo =
+        BucketInfo.newBuilder(bucketName)
+            .setIamConfiguration(
+                IamConfiguration.newBuilder().setIsUniformBucketLevelAccessEnabled(true).build())
+            .build();
+    bucket = storage.create(bucketInfo);
+    storageControl.createManagedFolder(
+        CreateManagedFolderRequest.newBuilder()
+            // Set project to "_" to signify global bucket
+            .setParent(BucketName.format("_", bucketName))
+            .setManagedFolder(ManagedFolder.newBuilder().build())
+            .setManagedFolderId(managedFolderId)
+            .build());
+  }
+
+  @After
+  public void tearDown() {
+    storageControl.deleteManagedFolder(
+        DeleteManagedFolderRequest.newBuilder()
+            .setName(ManagedFolderName.format("_", bucketName, managedFolderId))
+            .build());
+    storage.delete(bucketName);
+    storageControl.shutdown();
+  }
+
+  @Test
+  public void testGetManagedFolder() throws Exception {
+    GetManagedFolder.managedFolderGet(bucketName, managedFolderId);
+    String got = stdOut.getCapturedOutputAsUtf8String();
+    assertThat(got).contains(String.format(managedFolderId));
+  }
+}
diff --git a/samples/snippets/src/test/java/com/example/storage/managedfolders/ListManagedFolderTest.java b/samples/snippets/src/test/java/com/example/storage/managedfolders/ListManagedFolderTest.java
new file mode 100644
index 00000000..55bf404b
--- /dev/null
+++ b/samples/snippets/src/test/java/com/example/storage/managedfolders/ListManagedFolderTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.example.storage.managedfolders;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.cloud.storage.Bucket;
+import com.google.cloud.storage.BucketInfo;
+import com.google.cloud.storage.BucketInfo.IamConfiguration;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import com.google.cloud.storage.testing.RemoteStorageHelper;
+import com.google.cloud.testing.junit4.StdOutCaptureRule;
+import com.google.storage.control.v2.BucketName;
+import com.google.storage.control.v2.CreateManagedFolderRequest;
+import com.google.storage.control.v2.DeleteManagedFolderRequest;
+import com.google.storage.control.v2.ManagedFolder;
+import com.google.storage.control.v2.ManagedFolderName;
+import com.google.storage.control.v2.StorageControlClient;
+import java.io.IOException;
+import java.util.UUID;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ListManagedFolderTest {
+
+  @Rule public StdOutCaptureRule stdOut = new StdOutCaptureRule();
+
+  protected String bucketName;
+  protected Storage storage;
+  protected Bucket bucket;
+  protected String managedFolderId;
+  protected StorageControlClient storageControl;
+
+  @Before
+  public void setUp() throws IOException {
+    bucketName = RemoteStorageHelper.generateBucketName();
+    storageControl = StorageControlClient.create();
+    storage = StorageOptions.getDefaultInstance().getService();
+    managedFolderId = "new-managed-folder-" + UUID.randomUUID();
+    BucketInfo bucketInfo =
+        BucketInfo.newBuilder(bucketName)
+            .setIamConfiguration(
+                IamConfiguration.newBuilder().setIsUniformBucketLevelAccessEnabled(true).build())
+            .build();
+    bucket = storage.create(bucketInfo);
+    storageControl.createManagedFolder(
+        CreateManagedFolderRequest.newBuilder()
+            // Set project to "_" to signify global bucket
+            .setParent(BucketName.format("_", bucketName))
+            .setManagedFolder(ManagedFolder.newBuilder().build())
+            .setManagedFolderId(managedFolderId)
+            .build());
+  }
+
+  @After
+  public void tearDown() {
+    storageControl.deleteManagedFolder(
+        DeleteManagedFolderRequest.newBuilder()
+            .setName(ManagedFolderName.format("_", bucketName, managedFolderId))
+            .build());
+    storage.delete(bucketName);
+    storageControl.shutdown();
+  }
+
+  @Test
+  public void testListManagedFolder() throws Exception {
+    ListManagedFolders.managedFolderList(bucketName);
+    String got = stdOut.getCapturedOutputAsUtf8String();
+    assertThat(got).contains(String.format(managedFolderId));
+  }
+}