diff --git a/sdk-platform-java/gax-java/gax/pom.xml b/sdk-platform-java/gax-java/gax/pom.xml
index 2feb9c9aba81..4d92bdfd5255 100644
--- a/sdk-platform-java/gax-java/gax/pom.xml
+++ b/sdk-platform-java/gax-java/gax/pom.xml
@@ -115,6 +115,7 @@
com/google/api/gax/rpc/testing/**
com/google/api/gax/rpc/mtls/**
com/google/api/gax/util/**
+ com/google/api/gax/logging/**
**/native-image.properties
diff --git a/sdk-platform-java/java-showcase/gapic-showcase/pom.xml b/sdk-platform-java/java-showcase/gapic-showcase/pom.xml
index bc8d2cc812b6..85f2b44a6996 100644
--- a/sdk-platform-java/java-showcase/gapic-showcase/pom.xml
+++ b/sdk-platform-java/java-showcase/gapic-showcase/pom.xml
@@ -74,6 +74,16 @@
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+ **/ITActionableErrorsLogging.java
+
+
+
@@ -274,6 +284,7 @@
**/com/google/showcase/v1beta1/it/*.java
**/com/google/showcase/v1beta1/it/logging/ITLoggingDisabled.java
**/com/google/showcase/v1beta1/it/logging/ITLogging1x.java
+ **/com/google/showcase/v1beta1/it/logging/ITActionableErrorsLogging.java
@@ -318,6 +329,7 @@
**/com/google/showcase/v1beta1/it/*.java
**/com/google/showcase/v1beta1/it/logging/ITLoggingDisabled.java
**/com/google/showcase/v1beta1/it/logging/ITLogging.java
+ **/com/google/showcase/v1beta1/it/logging/ITActionableErrorsLogging.java
@@ -363,6 +375,7 @@
**/com/google/showcase/v1beta1/it/*.java
**/com/google/showcase/v1beta1/it/logging/ITLogging1x.java
**/com/google/showcase/v1beta1/it/logging/ITLogging.java
+ **/com/google/showcase/v1beta1/it/logging/ITActionableErrorsLogging.java
@@ -388,6 +401,44 @@
+
+ envVarTest
+
+
+ org.slf4j
+ slf4j-api
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ **/ITActionableErrorsLogging.java
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+
+ **/ITActionableErrorsLogging.java
+
+
+ ${project.basedir}/src/test/slf4j-test-provider
+
+
+
+
+
+
diff --git a/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/logging/ITActionableErrorsLogging.java b/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/logging/ITActionableErrorsLogging.java
new file mode 100644
index 000000000000..910ed08057e7
--- /dev/null
+++ b/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/logging/ITActionableErrorsLogging.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://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.showcase.v1beta1.it.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.google.api.client.http.LowLevelHttpRequest;
+import com.google.api.client.http.LowLevelHttpResponse;
+import com.google.api.client.testing.http.MockHttpTransport;
+import com.google.api.client.testing.http.MockLowLevelHttpRequest;
+import com.google.api.client.testing.http.MockLowLevelHttpResponse;
+import com.google.api.gax.core.NoCredentialsProvider;
+import com.google.api.gax.logging.TestLogger;
+import com.google.api.gax.rpc.ApiException;
+import com.google.api.gax.tracing.LoggingTracerFactory;
+import com.google.protobuf.Any;
+import com.google.rpc.ErrorInfo;
+import com.google.rpc.Status;
+import com.google.showcase.v1beta1.EchoClient;
+import com.google.showcase.v1beta1.EchoRequest;
+import com.google.showcase.v1beta1.EchoSettings;
+import com.google.showcase.v1beta1.it.util.TestClientInitializer;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.LoggerFactory;
+
+public class ITActionableErrorsLogging {
+
+ private static EchoClient grpcClient;
+ private static EchoClient httpjsonClient;
+ private TestLogger testLogger;
+
+ @BeforeAll
+ static void createClients() throws Exception {
+ grpcClient =
+ TestClientInitializer.createGrpcEchoClientOpentelemetry(new LoggingTracerFactory());
+ httpjsonClient =
+ TestClientInitializer.createHttpJsonEchoClientOpentelemetry(new LoggingTracerFactory());
+ }
+
+ @AfterAll
+ static void destroyClients() throws InterruptedException {
+ grpcClient.close();
+ httpjsonClient.close();
+
+ grpcClient.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS);
+ httpjsonClient.awaitTermination(
+ TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS);
+ }
+
+ @BeforeEach
+ void setupTestLogger() {
+ testLogger = (TestLogger) LoggerFactory.getLogger("com.google.api.gax.tracing.LoggingTracer");
+ testLogger.getMessageList().clear();
+ testLogger.getKeyValuePairsMap().clear();
+ }
+
+ private EchoRequest buildErrorRequest() {
+ ErrorInfo errorInfo =
+ ErrorInfo.newBuilder()
+ .setReason("TEST_REASON")
+ .setDomain("test.googleapis.com")
+ .putMetadata("test_metadata", "test_value")
+ .build();
+ Status status =
+ Status.newBuilder()
+ .setCode(3) // INVALID_ARGUMENT
+ .setMessage("This is a test error")
+ .addDetails(Any.pack(errorInfo))
+ .build();
+ return EchoRequest.newBuilder().setError(status).build();
+ }
+
+ @Test
+ void testGrpc_actionableErrorLogged() {
+ EchoRequest request = buildErrorRequest();
+
+ assertThrows(ApiException.class, () -> grpcClient.echo(request));
+
+ assertThat(testLogger.getMessageList().size()).isAtLeast(1);
+ String loggedMessage = testLogger.getMessageList().get(testLogger.getMessageList().size() - 1);
+
+ assertThat(loggedMessage).contains("This is a test error");
+
+ Map kvps = testLogger.getKeyValuePairsMap();
+ assertThat(kvps).containsEntry("rpc.system.name", "grpc");
+ assertThat(kvps).containsEntry("rpc.method", "google.showcase.v1beta1.Echo/Echo");
+ assertThat(kvps).containsEntry("rpc.response.status_code", "INVALID_ARGUMENT");
+ assertThat(kvps).containsEntry("error.type", "TEST_REASON");
+ assertThat(kvps).containsEntry("gcp.errors.domain", "test.googleapis.com");
+ assertThat(kvps).containsEntry("gcp.errors.metadata.test_metadata", "test_value");
+ }
+
+ @Test
+ void testHttpJson_actionableErrorLogged() throws Exception {
+ // The gapic-showcase server currently returns text/plain for failEchoWithDetails instead of
+ // JSON.
+ // Additionally, sending an ErrorInfo in a request over REST fails serialization.
+ // To test HTTP JSON actionable errors logic, we use a MockHttpTransport that simulates the
+ // correct JSON format.
+ MockHttpTransport mockTransport =
+ new MockHttpTransport() {
+ @Override
+ public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
+ return new MockLowLevelHttpRequest() {
+ @Override
+ public LowLevelHttpResponse execute() throws IOException {
+ MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
+ response.setStatusCode(409); // ABORTED
+ response.setContentType("application/json");
+ String jsonError =
+ "{\n"
+ + " \"error\": {\n"
+ + " \"code\": 409,\n"
+ + " \"message\": \"This is a mock JSON error generated by the server\",\n"
+ + " \"status\": \"ABORTED\",\n"
+ + " \"details\": [\n"
+ + " {\n"
+ + " \"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\",\n"
+ + " \"reason\": \"mock_error_reason\",\n"
+ + " \"domain\": \"mock.googleapis.com\",\n"
+ + " \"metadata\": {\"mock_key\": \"mock_value\"}\n"
+ + " }\n"
+ + " ]\n"
+ + " }\n"
+ + "}";
+ response.setContent(jsonError);
+ return response;
+ }
+ };
+ }
+ };
+
+ EchoSettings httpJsonEchoSettings =
+ EchoSettings.newHttpJsonBuilder()
+ .setCredentialsProvider(NoCredentialsProvider.create())
+ .setTransportChannelProvider(
+ EchoSettings.defaultHttpJsonTransportProviderBuilder()
+ .setHttpTransport(mockTransport)
+ .setEndpoint(TestClientInitializer.DEFAULT_HTTPJSON_ENDPOINT)
+ .build())
+ .build();
+
+ com.google.showcase.v1beta1.stub.EchoStubSettings echoStubSettings =
+ (com.google.showcase.v1beta1.stub.EchoStubSettings)
+ httpJsonEchoSettings.getStubSettings().toBuilder()
+ .setTracerFactory(new LoggingTracerFactory())
+ .build();
+ com.google.showcase.v1beta1.stub.EchoStub stub = echoStubSettings.createStub();
+ EchoClient mockHttpJsonClient = EchoClient.create(stub);
+
+ EchoRequest request = EchoRequest.newBuilder().build();
+
+ assertThrows(ApiException.class, () -> mockHttpJsonClient.echo(request));
+
+ assertThat(testLogger.getMessageList().size()).isAtLeast(1);
+ String loggedMessage = testLogger.getMessageList().get(testLogger.getMessageList().size() - 1);
+
+ assertThat(loggedMessage).contains("This is a mock JSON error generated by the server");
+
+ Map kvps = testLogger.getKeyValuePairsMap();
+ assertThat(kvps).containsEntry("rpc.system.name", "http");
+ assertThat(kvps).containsEntry("http.request.method", "POST");
+ assertThat(kvps).containsEntry("url.template", "v1beta1/echo:echo");
+ assertThat(kvps).containsEntry("rpc.response.status_code", "ABORTED");
+ assertThat(kvps).containsEntry("error.type", "mock_error_reason");
+ assertThat(kvps).containsEntry("gcp.errors.domain", "mock.googleapis.com");
+ assertThat(kvps).containsEntry("gcp.errors.metadata.mock_key", "mock_value");
+
+ mockHttpJsonClient.close();
+ mockHttpJsonClient.awaitTermination(
+ TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS);
+ }
+}
diff --git a/sdk-platform-java/java-showcase/gapic-showcase/src/test/slf4j-test-provider/META-INF/services/org.slf4j.spi.SLF4JServiceProvider b/sdk-platform-java/java-showcase/gapic-showcase/src/test/slf4j-test-provider/META-INF/services/org.slf4j.spi.SLF4JServiceProvider
new file mode 100644
index 000000000000..b97d560115b5
--- /dev/null
+++ b/sdk-platform-java/java-showcase/gapic-showcase/src/test/slf4j-test-provider/META-INF/services/org.slf4j.spi.SLF4JServiceProvider
@@ -0,0 +1 @@
+com.google.api.gax.logging.TestServiceProvider
diff --git a/sdk-platform-java/java-showcase/pom.xml b/sdk-platform-java/java-showcase/pom.xml
index a241e7677e03..41efc38e5311 100644
--- a/sdk-platform-java/java-showcase/pom.xml
+++ b/sdk-platform-java/java-showcase/pom.xml
@@ -35,6 +35,11 @@
pom
import
+
+ org.slf4j
+ slf4j-api
+ 2.0.16
+
com.google.api.grpc
proto-gapic-showcase-v1beta1