From 831c15ea98b78bef8f2b88b5e50044eea2fd54ca Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Thu, 30 Jul 2026 17:43:36 -0400 Subject: [PATCH 1/6] deps: Upgrade grpc-java to v1.83.0 --- grpc-gcp-java/pom.xml | 2 +- librarian.yaml | 2 +- sdk-platform-java/gapic-generator-java-pom-parent/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/grpc-gcp-java/pom.xml b/grpc-gcp-java/pom.xml index 17997ee07915..8f7355413728 100644 --- a/grpc-gcp-java/pom.xml +++ b/grpc-gcp-java/pom.xml @@ -69,7 +69,7 @@ 2.13.2 33.6.0-jre 4.33.6 - 1.82.2 + 1.83.0 3.0.2 4.13.2 0.31.1 diff --git a/librarian.yaml b/librarian.yaml index fe4c6889f47f..0fd0b4b58840 100644 --- a/librarian.yaml +++ b/librarian.yaml @@ -30,7 +30,7 @@ tools: classifier: all-deps packaging: jar - name: protoc-gen-java_grpc - version: 1.82.2 + version: 1.83.0 group_id: io.grpc artifact_id: protoc-gen-grpc-java classifier: linux-x86_64 diff --git a/sdk-platform-java/gapic-generator-java-pom-parent/pom.xml b/sdk-platform-java/gapic-generator-java-pom-parent/pom.xml index 4b7b477219c5..85cf893a4b8c 100644 --- a/sdk-platform-java/gapic-generator-java-pom-parent/pom.xml +++ b/sdk-platform-java/gapic-generator-java-pom-parent/pom.xml @@ -27,7 +27,7 @@ 1.3.2 - 1.82.2 + 1.83.0 2.2.0 2.13.2 33.6.0-jre From 1f4d589195762203f779e47f3414403882718dc5 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Fri, 31 Jul 2026 12:14:26 -0400 Subject: [PATCH 2/6] chore: Resolve upper bounds issues --- google-auth-library-java/pom.xml | 2 +- grpc-gcp-java/pom.xml | 2 +- java-bigtable/pom.xml | 2 +- java-datastore/pom.xml | 2 +- sdk-platform-java/gapic-generator-java-pom-parent/pom.xml | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/google-auth-library-java/pom.xml b/google-auth-library-java/pom.xml index 374ec0ed8737..fca2a8fabe56 100644 --- a/google-auth-library-java/pom.xml +++ b/google-auth-library-java/pom.xml @@ -85,7 +85,7 @@ 0.9.0-proto3 1.15.0 2.0.17 - 2.13.2 + 2.14.0 2.67.0-SNAPSHOT 1.18.0 3.5.2 diff --git a/grpc-gcp-java/pom.xml b/grpc-gcp-java/pom.xml index 8f7355413728..b9d8632c88d1 100644 --- a/grpc-gcp-java/pom.xml +++ b/grpc-gcp-java/pom.xml @@ -66,7 +66,7 @@ 1.11.0 2.48.0 2.2.0 - 2.13.2 + 2.14.0 33.6.0-jre 4.33.6 1.83.0 diff --git a/java-bigtable/pom.xml b/java-bigtable/pom.xml index c6bd75e5f4f7..454eede9bfbd 100644 --- a/java-bigtable/pom.xml +++ b/java-bigtable/pom.xml @@ -144,7 +144,7 @@ github google-cloud-bigtable-parent https://googleapis.dev/java/google-api-grpc/latest - 2.48.0 + 2.50.0 -g diff --git a/java-datastore/pom.xml b/java-datastore/pom.xml index 674bdac579f4..30f3d56b7f25 100644 --- a/java-datastore/pom.xml +++ b/java-datastore/pom.xml @@ -144,7 +144,7 @@ github google-cloud-datastore-parent https://googleapis.dev/java/google-api-grpc/latest - 2.48.0 + 2.50.0 -g diff --git a/sdk-platform-java/gapic-generator-java-pom-parent/pom.xml b/sdk-platform-java/gapic-generator-java-pom-parent/pom.xml index 85cf893a4b8c..c810ed161d02 100644 --- a/sdk-platform-java/gapic-generator-java-pom-parent/pom.xml +++ b/sdk-platform-java/gapic-generator-java-pom-parent/pom.xml @@ -29,11 +29,11 @@ 1.3.2 1.83.0 2.2.0 - 2.13.2 + 2.14.0 33.6.0-jre 4.33.6 1.62.0 - 2.48.0 + 2.50.0 1.0.0 3.1 1.7.0 From 71e4a1e9a9c5f3dd4a0849123ee352614a6de5f7 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Fri, 31 Jul 2026 12:21:10 -0400 Subject: [PATCH 3/6] chore: Fix missing auth enforcer upgrade --- google-auth-library-java/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-auth-library-java/pom.xml b/google-auth-library-java/pom.xml index fca2a8fabe56..260fe6bfc286 100644 --- a/google-auth-library-java/pom.xml +++ b/google-auth-library-java/pom.xml @@ -80,7 +80,7 @@ 2.0.33 3.0.2 false - 2.48.0 + 2.50.0 4.33.6 0.9.0-proto3 1.15.0 From 10e8a8f72f10a152753ad4c056ceaf3caacbf23c Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Fri, 31 Jul 2026 12:29:22 -0400 Subject: [PATCH 4/6] chore: Update errorprone versio in grpc-gcp --- grpc-gcp-java/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grpc-gcp-java/pom.xml b/grpc-gcp-java/pom.xml index b9d8632c88d1..9a6e523f52ec 100644 --- a/grpc-gcp-java/pom.xml +++ b/grpc-gcp-java/pom.xml @@ -64,7 +64,7 @@ grpc-gcp 2.67.0-SNAPSHOT 1.11.0 - 2.48.0 + 2.50.0 2.2.0 2.14.0 33.6.0-jre From 16c429df774f68287e51e59be7e0830e6059ce77 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Fri, 31 Jul 2026 05:04:17 +0000 Subject: [PATCH 5/6] feat(pqc): upgrade gRPC to v1.83.0 and add gRPC PQC Showcase integration tests - Upgrade from 1.82.2 to 1.83.0 in gapic-generator-java-pom-parent and grpc-gcp-java. - Upgrade to 2.14.0 and to 2.50.0 to satisfy Maven Enforcer rules for gRPC 1.83.0. - Add testGrpcPqc_withTls Showcase TLS integration test in ITPostQuantumCryptography.java using built-in default gRPC transport, configured to trust the Showcase TLS server certificate directly. - Include concise Javadoc documenting gRPC 1.83.0 built-in PQC behavior and explaining the purpose of GrpcTlsCapturingClientInterceptor and HTTP/2 metadata helper methods. - Simplify GrpcTlsCapturingClientInterceptor to only capture initial response headers (onHeaders). - Assert negotiated group is equal to EXPECTED_PQC_GROUP for gRPC without asserting a supported groups list. - Leave all HTTP/JSON tests and setUp() 100% untouched. - Exclude all mTLS test resources, keys, certs, and mTLS logic. TAG=agy CONV=385b9ab5-874c-4c9a-b331-66dab51fef61 --- .../v1beta1/it/ITPostQuantumCryptography.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITPostQuantumCryptography.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITPostQuantumCryptography.java index e1f2ed82560d..0228795f3f39 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITPostQuantumCryptography.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITPostQuantumCryptography.java @@ -21,6 +21,7 @@ import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.gax.core.NoCredentialsProvider; +import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; import com.google.api.gax.httpjson.HttpJsonConscryptUtils; import com.google.api.gax.httpjson.HttpJsonMetadata; import com.google.api.gax.httpjson.InstantiatingHttpJsonChannelProvider; @@ -29,6 +30,16 @@ import com.google.showcase.v1beta1.EchoResponse; import com.google.showcase.v1beta1.EchoSettings; import com.google.showcase.v1beta1.it.util.HttpJsonCapturingClientInterceptor; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.ForwardingClientCallListener; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; import java.io.File; import java.io.InputStream; import java.nio.file.Files; @@ -266,4 +277,104 @@ private static KeyStore loadCaCert(String certPath) throws Exception { } return trustStore; } + + /** + * Integration test to verify Post-Quantum Cryptography (PQC) TLS negotiation for gRPC clients. + * + *

In gRPC-Java 1.83.0+, the default Netty transport (`grpc-netty-shaded`) bundles BoringSSL + * (`netty-tcnative-boringssl-static`) with built-in PQC hybrid key exchange support (e.g., + * X25519MLKEM768). No custom socket configurator or security provider swapping is needed. + * + *

Because the local Showcase test server uses a self-signed CA certificate (written to {@link + * #DEFAULT_CA_CERT_PATH}), we configure the gRPC transport channel builder directly to trust this + * certificate via {@link GrpcSslContexts#forClient()}. This avoids mutating global JVM system + * properties in {@code setUp()} and ensures HTTP/JSON tests remain completely isolated. + */ + @Test + void testGrpcPqc_withTls() throws Exception { + GrpcTlsCapturingClientInterceptor interceptor = new GrpcTlsCapturingClientInterceptor(); + + InstantiatingGrpcChannelProvider transportChannelProvider = + EchoSettings.defaultGrpcTransportProviderBuilder() + .setEndpoint(SECURE_ENDPOINT) + .setInterceptorProvider(() -> Collections.singletonList(interceptor)) + .setChannelConfigurator( + managedChannelBuilder -> { + if (managedChannelBuilder instanceof NettyChannelBuilder) { + try { + // Explicitly trust the self-signed CA certificate created by the local + // Showcase TLS connection without altering JVM-wide SSL trust stores. + ((NettyChannelBuilder) managedChannelBuilder) + .sslContext( + GrpcSslContexts.forClient() + .trustManager(new File(DEFAULT_CA_CERT_PATH)) + .build()); + } catch (Exception e) { + throw new RuntimeException("Failed to configure gRPC SSL context", e); + } + } + return managedChannelBuilder; + }) + .build(); + + EchoSettings settings = + EchoSettings.newBuilder() + .setCredentialsProvider(NoCredentialsProvider.create()) + .setTransportChannelProvider(transportChannelProvider) + .build(); + + try (EchoClient client = EchoClient.create(settings)) { + EchoResponse response = + client.echo(EchoRequest.newBuilder().setContent("pqc-grpc-tls-test").build()); + assertThat(response.getContent()).isEqualTo("pqc-grpc-tls-test"); + + Metadata capturedHeaders = interceptor.capturedMetadata; + assertThat(capturedHeaders).isNotNull(); + + // Verify that TLS 1.3 key exchange negotiated the expected PQC hybrid group (X25519MLKEM768). + String negotiatedGroup = getGrpcSingleHeaderString(capturedHeaders, TLS_GROUP_HEADER); + assertThat(negotiatedGroup).isEqualTo(EXPECTED_PQC_GROUP); + } + } + + /** + * Private gRPC client interceptor to capture the response headers from the Showcase server to + * verify the PQC algorithm. + */ + private static class GrpcTlsCapturingClientInterceptor implements ClientInterceptor { + final Metadata capturedMetadata = new Metadata(); + + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + return new ForwardingClientCall.SimpleForwardingClientCall( + next.newCall(method, callOptions)) { + @Override + public void start(Listener responseListener, Metadata headers) { + super.start( + new ForwardingClientCallListener.SimpleForwardingClientCallListener( + responseListener) { + @Override + public void onHeaders(Metadata headers) { + capturedMetadata.merge(headers); + super.onHeaders(headers); + } + }, + headers); + } + }; + } + } + + /** + * Private helper method required to extract a single string header from gRPC {@link Metadata}. + * + * @param metadata the captured gRPC response metadata + * @param name the case-insensitive HTTP/2 header name + * @return the string header value, or {@code null} if not present + */ + private static String getGrpcSingleHeaderString(Metadata metadata, String name) { + Metadata.Key key = Metadata.Key.of(name, Metadata.ASCII_STRING_MARSHALLER); + return metadata.get(key); + } } From 5076794444303f26257b716ef5ea8495667c09b9 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Fri, 31 Jul 2026 19:19:29 +0000 Subject: [PATCH 6/6] fix(storage): use JsonUtils for StorageObject serialization in resumable writes and read channels When upgrading gRPC to v1.83.0 (and Gson to 2.14.0), Gson PR #3006 added strict duplicate key detection in MapTypeAdapterFactory.Adapter.read ('if (map.containsKey(key)) throw ...'). Because com.google.api.services.storage.model.StorageObject extends com.google.api.client.util.GenericData (which implements java.util.Map), raw Gson selects MapTypeAdapterFactory when serializing and deserializing StorageObject. However, GenericData.containsKey returns true for all @Key annotated fields even before they are set, causing raw Gson.fromJson to throw JsonSyntaxException ('duplicate key') when deserializing StorageObject. This replaces raw Gson serialization and deserialization of StorageObject in ApiaryReadRequest and JsonResumableWrite with Google API Client's official JSON parser/serializer via JsonUtils (backed by JsonObjectParser / GsonFactory), resolving the duplicate key deserialization failure in SerializationTest. --- .../ApiaryUnbufferedReadableByteChannel.java | 9 ++------ .../cloud/storage/JsonResumableWrite.java | 10 ++------- .../com/google/cloud/storage/JsonUtils.java | 22 +++++++++++++++++++ 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedReadableByteChannel.java b/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedReadableByteChannel.java index 781c449369cb..25757921ba54 100644 --- a/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedReadableByteChannel.java +++ b/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedReadableByteChannel.java @@ -41,14 +41,11 @@ import com.google.common.hash.HashingInputStream; import com.google.common.io.BaseEncoding; import com.google.common.primitives.Ints; -import com.google.gson.Gson; -import com.google.gson.stream.JsonReader; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; -import java.io.StringReader; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.channels.Channels; @@ -381,7 +378,6 @@ private static String getHeaderValue(@NonNull HttpHeaders headers, @NonNull Stri @Immutable static final class ApiaryReadRequest implements Serializable { private static final long serialVersionUID = -4059435314115374448L; - private static final Gson gson = new Gson(); @NonNull private transient StorageObject object; @NonNull private final Map options; @NonNull private final ByteRangeSpec byteRangeSpec; @@ -450,7 +446,7 @@ private String getObjectJson() { if (objectJson == null) { synchronized (this) { if (objectJson == null) { - objectJson = gson.toJson(object); + objectJson = JsonUtils.objectToJson(object); } } } @@ -464,8 +460,7 @@ private void writeObject(ObjectOutputStream out) throws IOException { private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); - JsonReader jsonReader = gson.newJsonReader(new StringReader(this.objectJson)); - this.object = gson.fromJson(jsonReader, StorageObject.class); + this.object = JsonUtils.jsonToObject(this.objectJson, StorageObject.class); } } } diff --git a/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java b/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java index b2347c47f3cf..3fef2dcbb9dc 100644 --- a/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java +++ b/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java @@ -22,13 +22,10 @@ import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableMap; -import com.google.gson.Gson; -import com.google.gson.stream.JsonReader; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; -import java.io.StringReader; import java.util.Map; import java.util.Objects; import org.checkerframework.checker.lock.qual.GuardedBy; @@ -37,8 +34,6 @@ final class JsonResumableWrite implements Serializable { private static final long serialVersionUID = 7934407897802252292L; - private static final Gson gson = new Gson(); - @MonotonicNonNull private transient StorageObject object; @MonotonicNonNull private transient Hasher hasher; @MonotonicNonNull private transient Crc32cValue cumulativeCrc32c; @@ -152,7 +147,7 @@ private String getObjectJson() { if (objectJson == null) { synchronized (this) { if (objectJson == null) { - objectJson = gson.toJson(object); + objectJson = JsonUtils.objectToJson(object); base64CumulativeCrc32c = Utils.crc32cCodec.encode(cumulativeCrc32c.getValue()); } } @@ -167,8 +162,7 @@ private void writeObject(ObjectOutputStream out) throws IOException { private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); - JsonReader jsonReader = gson.newJsonReader(new StringReader(this.objectJson)); - this.object = gson.fromJson(jsonReader, StorageObject.class); + this.object = JsonUtils.jsonToObject(this.objectJson, StorageObject.class); if (base64CumulativeCrc32c != null) { Integer decode = Utils.crc32cCodec.decode(base64CumulativeCrc32c); if (decode == 0) { diff --git a/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonUtils.java b/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonUtils.java index f5297ee837c0..6bc8d27d6e44 100644 --- a/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonUtils.java +++ b/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonUtils.java @@ -53,6 +53,28 @@ final class JsonUtils { private JsonUtils() {} + static String objectToJson(T object) { + if (object == null) { + return null; + } + try { + return jop.getJsonFactory().toString(object); + } catch (IOException e) { + throw StorageException.coalesce(e); + } + } + + static T jsonToObject(String json, Class clazz) { + if (json == null) { + return null; + } + try { + return jop.parseAndClose(new StringReader(json), clazz); + } catch (IOException e) { + throw StorageException.coalesce(e); + } + } + /** * Given a GenericJson src, and a list of {@code fieldsForOutput} create a new GenericJson where * every field specified in {@code fieldsForOutput} is present. If a field exists in {@code src}