Repository navigation
Open
Labels
api: spannerIssues related to the Spanner API.Issues related to the Spanner API.
Description
I'd like to report a NullPointerException in HttpJsonSpannerStub generated by gapic-generator. I understand the code is not intended for end-user use, but I'm reporting it in case others encounter similar issues
Environment details
- OS type and version: macOS 15.3
- Java version: 11
- version(s): google-cloud-spanner 6.86.6
Steps to reproduce
- Create a Spanner table with
CREATE TABLE table1 (k STRING(100), v INT64) PRIMARY KEY (k). - Add a row with the key
kas "k1". - Run the following code which writes a row with
kas "k1". This will result in an error status response from Cloud Spanner.
An exception with an appropriate error message, such as "Row [k1] in table table1 already exists," is expected. However, a NullPointerException is thrown instead.
Code example
import com.google.api.gax.rpc.ServerStream;
import com.google.api.gax.rpc.ServerStreamingCallable;
import com.google.cloud.spanner.v1.stub.SpannerStub;
import com.google.cloud.spanner.v1.stub.SpannerStubSettings;
import com.google.protobuf.ListValue;
import com.google.protobuf.Value;
import com.google.spanner.v1.*;
import java.io.IOException;
public class SpannerHttpJsonStubMain {
public static void main(String[] args) throws IOException {
SpannerStubSettings stubSettings = SpannerStubSettings.newHttpJsonBuilder().build();
try (SpannerStub stub = stubSettings.createStub()) {
Session session = stub.createSessionCallable()
.call(CreateSessionRequest.newBuilder()
.setDatabase("projects/<REDACTED>/instances/spanner-1/databases/db-1")
.build());
BatchWriteRequest request = BatchWriteRequest.newBuilder()
.setSession(session.getName())
.addMutationGroups(BatchWriteRequest.MutationGroup.newBuilder()
.addMutations(Mutation.newBuilder()
.setInsert(Mutation.Write.newBuilder()
// SCHEMA: CREATE TABLE table1 (k STRING(100), v INT64) PRIMARY KEY (k)
.setTable("table1")
.addColumns("k")
.addColumns("v")
// The key "k1" already exists in the table. Therefore, this request will result in an error
// response.
.addValues(ListValue.newBuilder()
.addValues(Value.newBuilder().setStringValue("k1"))
.addValues(Value.newBuilder().setStringValue("1"))))))
.build();
ServerStreamingCallable<BatchWriteRequest, BatchWriteResponse> callable = stub.batchWriteCallable();
ServerStream<BatchWriteResponse> responseStream = callable.call(request);
for (BatchWriteResponse response : responseStream) {
System.out.println("response = " + response);
}
}
}
}Stack trace
The code was run with the JVM option -Djava.util.logging.config.file=logging.properties to enable HTTP transport debug logging. The logging.properties file is here. The output shows that parsing DebugInfo failed because TypedRegistry was null in some location
... skipped ...
Feb 11, 2025 11:21:51 PM com.google.api.client.http.HttpResponse <init>
CONFIG: -------------- RESPONSE --------------
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
Transfer-Encoding: chunked
Server: ESF
Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
Server-Timing: gfet4t7; dur=148
Content-Encoding: gzip
Vary: Origin
Vary: X-Origin
Vary: Referer
X-XSS-Protection: 0
Date: Wed, 12 Feb 2025 07:21:51 GMT
Content-Type: application/json; charset=UTF-8
Feb 11, 2025 11:21:51 PM com.google.api.client.util.LoggingByteArrayOutputStream close
CONFIG: Total: 254 bytes
Feb 11, 2025 11:21:51 PM com.google.api.client.util.LoggingByteArrayOutputStream close
CONFIG: [{
"indexes": [
0
],
"status": {
"code": 6,
"message": "Row [k1] in table table1 already exists",
"details": [
{
"@type": "type.googleapis.com/google.rpc.DebugInfo",
"detail": "table1(k1)"
}
]
}
}
]
Exception in thread "main" com.google.api.gax.rpc.CancelledException: Exception in message delivery
at com.google.api.gax.rpc.ApiExceptionFactory.createException(ApiExceptionFactory.java:48)
at com.google.api.gax.httpjson.HttpJsonApiExceptionFactory.createApiException(HttpJsonApiExceptionFactory.java:76)
at com.google.api.gax.httpjson.HttpJsonApiExceptionFactory.create(HttpJsonApiExceptionFactory.java:58)
at com.google.api.gax.httpjson.HttpJsonExceptionResponseObserver.onErrorImpl(HttpJsonExceptionResponseObserver.java:82)
at com.google.api.gax.rpc.StateCheckingResponseObserver.onError(StateCheckingResponseObserver.java:84)
at com.google.api.gax.httpjson.HttpJsonDirectStreamController$ResponseObserverAdapter.onClose(HttpJsonDirectStreamController.java:125)
at com.google.api.gax.httpjson.HttpJsonClientCallImpl$OnCloseNotificationTask.call(HttpJsonClientCallImpl.java:551)
at com.google.api.gax.httpjson.HttpJsonClientCallImpl.notifyListeners(HttpJsonClientCallImpl.java:390)
at com.google.api.gax.httpjson.HttpJsonClientCallImpl.deliver(HttpJsonClientCallImpl.java:317)
at com.google.api.gax.httpjson.HttpJsonClientCallImpl.setResult(HttpJsonClientCallImpl.java:163)
at com.google.api.gax.httpjson.HttpRequestRunnable.run(HttpRequestRunnable.java:148)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1570)
Suppressed: java.lang.RuntimeException: Asynchronous task failed
at com.google.api.gax.rpc.ServerStreamIterator.hasNext(ServerStreamIterator.java:105)
at SpannerHttpJsonStubMain.main(SpannerHttpJsonStubMain.java:38)
Caused by: com.google.api.gax.httpjson.HttpJsonStatusRuntimeException: Exception in message delivery
at com.google.api.gax.httpjson.HttpJsonClientCallImpl.deliver(HttpJsonClientCallImpl.java:367)
... 8 more
Caused by: com.google.api.gax.httpjson.RestSerializationException: Failed to parse response message
at com.google.api.gax.httpjson.ProtoRestSerializer.fromJson(ProtoRestSerializer.java:107)
at com.google.api.gax.httpjson.ProtoMessageResponseParser.parse(ProtoMessageResponseParser.java:76)
at com.google.api.gax.httpjson.ProtoMessageResponseParser.parse(ProtoMessageResponseParser.java:41)
at com.google.api.gax.httpjson.HttpJsonClientCallImpl.consumeMessageFromStream(HttpJsonClientCallImpl.java:430)
at com.google.api.gax.httpjson.HttpJsonClientCallImpl.deliver(HttpJsonClientCallImpl.java:362)
... 8 more
Caused by: com.google.protobuf.InvalidProtocolBufferException: Cannot invoke "com.google.protobuf.TypeRegistry.getDescriptorForTypeUrl(String)" because "this.registry" is null
at com.google.protobuf.util.JsonFormat$ParserImpl.merge(JsonFormat.java:1309)
at com.google.protobuf.util.JsonFormat$Parser.merge(JsonFormat.java:463)
at com.google.api.gax.httpjson.ProtoRestSerializer.fromJson(ProtoRestSerializer.java:104)
... 12 more
Caused by: java.lang.NullPointerException: Cannot invoke "com.google.protobuf.TypeRegistry.getDescriptorForTypeUrl(String)" because "this.registry" is null
at com.google.protobuf.util.JsonFormat$ParserImpl.mergeAny(JsonFormat.java:1507)
at com.google.protobuf.util.JsonFormat$ParserImpl.access$2000(JsonFormat.java:1276)
at com.google.protobuf.util.JsonFormat$ParserImpl$1.merge(JsonFormat.java:1343)
at com.google.protobuf.util.JsonFormat$ParserImpl.merge(JsonFormat.java:1432)
at com.google.protobuf.util.JsonFormat$ParserImpl.parseFieldValue(JsonFormat.java:1995)
at com.google.protobuf.util.JsonFormat$ParserImpl.mergeRepeatedField(JsonFormat.java:1710)
at com.google.protobuf.util.JsonFormat$ParserImpl.mergeField(JsonFormat.java:1642)
at com.google.protobuf.util.JsonFormat$ParserImpl.mergeMessage(JsonFormat.java:1477)
at com.google.protobuf.util.JsonFormat$ParserImpl.merge(JsonFormat.java:1435)
at com.google.protobuf.util.JsonFormat$ParserImpl.parseFieldValue(JsonFormat.java:1995)
at com.google.protobuf.util.JsonFormat$ParserImpl.mergeField(JsonFormat.java:1646)
at com.google.protobuf.util.JsonFormat$ParserImpl.mergeMessage(JsonFormat.java:1477)
at com.google.protobuf.util.JsonFormat$ParserImpl.merge(JsonFormat.java:1435)
at com.google.protobuf.util.JsonFormat$ParserImpl.merge(JsonFormat.java:1299)
... 14 more
Metadata
Metadata
Assignees
Labels
api: spannerIssues related to the Spanner API.Issues related to the Spanner API.
Activity
baeminbo commentedon Feb 12, 2025
This issue can be resolved by passing a
HttpJsonCallContextwithTypeRegistrythat includesDebugInfoto thecall()method. The following example adds all proto types within ErrorDetails to theTypedRegistryand passing it tocall(). The full code is available here.The example parsed the
DebugInfosuccessfully as shown below.blakeli0 commentedon Feb 19, 2025
@rahul2393 Does Spanner support httpjson officially? @baeminbo I understand there is a potential issue in Gax, but if Spanner does not support httpjson officially, then it changes the priority as well.
In addition, how did you find this issue? Are there any customers experiencing this issue?
baeminbo commentedon Feb 20, 2025
It appears that httpjson is not officially supported by Spanner library. I'm not aware of any customers using HttpJson for Spanner, so this issue has very low priority. I discovered this while reviewing gapic-generated code for various Cloud services, including GCS, Spanner, and Pub/Sub. There are no known customer issues related to this at this time.
blakeli0 commentedon Feb 20, 2025
That's great to know, thanks! If that's the case, do you mind creating a separate issue in sdk-platform-java repo so we can discuss and prioritize without our team? I know there is a similar issue #12465, but that issue is more focused on how to parse ErrorDetails in exceptions, this issue is more regarding how to parse Any type in httpjson responses.
baeminbo commentedon Feb 22, 2025
I filed googleapis/sdk-platform-java#3652. Thanks!
[-]NullPointerException occurring in HttpJsonSpannerStub.batchWriteCallable() when handling an error status response.[/-][+][java-spanner] NullPointerException occurring in HttpJsonSpannerStub.batchWriteCallable() when handling an error status response.[/+]