GRPC-Kotlin-Multiplatform icon indicating copy to clipboard operation
GRPC-Kotlin-Multiplatform copied to clipboard

java.lang.IllegalArgumentException: Invalid character ':' in key name ':status'

Open TheLortex opened this issue 4 months ago • 2 comments

Hi, thank you for your work on this library !

I've started experimenting with various error cases and one of them crashes the library at least on Desktop/JVM:

déc. 05, 2025 4:42:54 PM io.grpc.internal.ClientCallImpl closeObserver
AVERTISSEMENT: Exception thrown by onClose() in ClientCall
java.lang.IllegalArgumentException: Invalid character ':' in key name ':status'
        at com.google.common.base.Preconditions.checkArgument(Preconditions.java:273)
        at io.grpc.Metadata$Key.validateName(Metadata.java:754)
        at io.grpc.Metadata$Key.<init>(Metadata.java:762)
        at io.grpc.Metadata$Key.<init>(Metadata.java:671)
        at io.grpc.Metadata$AsciiKey.<init>(Metadata.java:971)
        at io.grpc.Metadata$AsciiKey.<init>(Metadata.java:966)
        at io.grpc.Metadata$Key.of(Metadata.java:708)
        at io.grpc.Metadata$Key.of(Metadata.java:704)
        at io.github.timortel.kmpgrpc.core.ConversionutilKt.getKmMetadata(conversionutil.kt:40)
        at io.github.timortel.kmpgrpc.core.internal.ClientInterceptorImpl$interceptCall$1$start$1.onClose(ClientInterceptorImpl.kt:69)
        at io.grpc.internal.DelayedClientCall$DelayedListener$3.run(DelayedClientCall.java:487)
        at io.grpc.internal.DelayedClientCall$DelayedListener.delayOrExecute(DelayedClientCall.java:451)
        at io.grpc.internal.DelayedClientCall$DelayedListener.onClose(DelayedClientCall.java:484)
        at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:565)
        at io.grpc.internal.ClientCallImpl.access$100(ClientCallImpl.java:72)
        at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:733)
        at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:714)
        at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
        at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:133)
        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:1583)

This error occurs when I try to call a server responding with 502 Bad Gateway. My backend configuration uses a proxy pointing to a GRPC server. When the GRPC server is down, the proxy returns 502.

The onClose operation seems to happen on a separate thread, so there is no way for me to catch this exception.

TheLortex avatar Dec 05 '25 15:12 TheLortex

Thank you for reporting this problem. Seems like the parsing of the key value pair causes the crash. I will have a look at this problem.

Can you maybe describe your setup in a bit more detail? Which grpc version and flavor (Java, Python, etc) is your server using?

TimOrtel avatar Dec 07 '25 10:12 TimOrtel

It should be simple to reproduce, because you don't even need a GRPC server. I reproduced the error by using the https://example.com url for building the grpc channel:

Channel.Builder.forAddress("example.com", 443).build()

The exception occurs when the server the client connects to understands HTTP/2, but is not a GRPC server.

TheLortex avatar Dec 08 '25 09:12 TheLortex

I have the same error as soon as I add an interceptor, if the backend is unavailable (orwith example.com which is http 2.0 without GRPC). The issue happens even if I just return the original metadata or add some log like in the example. If I remove the interceptor, I have a proper statusexception as a response: io.github.timortel.kmpgrpc.core.StatusException: Status(code=UNAVAILABLE, statusMessage=HTTP status code 503 or something like that.

With an interceptor I have the IllegalArgumentException from io.grpc/conversionutil.kt.

I guess somewhere the HTTP/2 pseudo-headers should be filtered. Filtering in onClose, does not solve it, because the error happens before that is called, I think probably conversionutil.kt JvmMetadata.kmMetadata is the issue.

Until this is fixed I had to create the channel in JVM/Android by building a Managed channel with it's own client interceptor and hack into this library via reflection to call the private constructor of Channel.

@TimOrtel Can you take a look?

aszorenyi avatar Feb 13 '26 15:02 aszorenyi