Repository navigation
[Java] Improve ProtoCoder decode error for raw Message type - #40904
Brijesh-Thakkar wants to merge 1 commit into
Conversation
…9713) A ProtoCoder for the raw com.google.protobuf.Message interface (for example inferred from a generic T extends Message) cannot find a Parser, and failed with a bare NoSuchMethodException on the worker. Wrap it in an IllegalArgumentException that explains the cause and suggests a concrete message class, DynamicProtoCoder, or setCoder(). No behaviour change: the same inputs fail, with a clearer message. Encoding with such a coder is unchanged and covered by a test. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
📋 Review Summary
This PR improves the error message in ProtoCoder.getParser() when a NoSuchMethodException occurs while looking up getDefaultInstance() (such as when a ProtoCoder is created for the raw com.google.protobuf.Message interface due to type erasure). It preserves existing behavior, exception types, and causes while giving actionable guidance to users.
🔍 General Feedback
- The implementation is clean, minimal, and backwards-compatible (preserving
IllegalArgumentExceptionwith the underlyingNoSuchMethodExceptionas its cause). - Unit tests cover both the improved error message on decode and the existing behavior on encode.
- Regarding the follow-up mentioned in the PR description about rejecting raw
Message.classinProtoCoderProvider.coderFor: keeping this PR scoped to the decode error message is a safe choice, and rejecting rawMessage/ abstract classes during coder inference inProtoCoderProviderwould indeed be a good candidate for a separate follow-up discussion/PR.
💡 Tip: Comment /gemini review on this pull request to trigger a new Gemini code review.
|
Assigning reviewers: R: @damccorm for label java. Note: If you would like to opt out of this review, comment Available commands:
The PR bot will only process comments in the main thread (not review comments). |
Addresses #19713.
ProtoCoderstores only the message class and finds itsParserlazily throughgetDefaultInstance(). For a genericT extends Messagethe type is erased to thecom.google.protobuf.Messageinterface, which has nogetDefaultInstance(). A coder for it can never decode, and the failure on the worker was a bareNoSuchMethodException: com.google.protobuf.Message.getDefaultInstance(), as reported in the issue.The concrete type is not recoverable at decode time (the wire format carries no type information), so this PR does not try to make that coder work and does not change what succeeds or fails. It only makes the failure understandable:
ProtoCoder.getParser()now throws anIllegalArgumentException(the same exception type as before, with the originalNoSuchMethodExceptionas the cause) saying why the parser cannot be found and to use a concrete generated message class,DynamicProtoCoder, orsetCoder(). The existingDynamicMessagepath is untouched.Earlier attempts (#34194, #37005) added an instantiation fallback. That cannot work for an interface, and their tests did not fail before the change. The decode test here fails on master (old message, no guidance) and passes with the change.
Behaviour change: none beyond the error message. Encoding with a raw-
Messagecoder still works and is covered by a test.Testing:
./gradlew :sdks:java:extensions:protobuf:spotlessCheck :sdks:java:extensions:protobuf:test: 196 tests, all pass.testDecodeWithRawMessageClassfails on master (oldNoSuchMethodExceptionmessage), passes with the change.testEncodeWithRawMessageClasspasses before and after.ProtoCoder):PubsubIOTest,PubsubUnboundedSinkTest,PubsubMessageWith*CoderTest,BigQueryIOStorageReadTest: 133 tests, all pass.Possible follow-up (for the maintainer to decide)
Coder inference (
CoderRegistry) still returns aProtoCoder<Message>silently for the rawMessagetype, so the failure only appears at decode time on the worker. A small follow-up would makeProtoCoderProvider.coderForthrowCannotProvideCoderExceptionfor it, so the failure moves to pipeline construction (test:testRegistryRejectsRawMessageType). I held it back because it is a behaviour change: a pipeline that relied on inference for an erasedT extends Messageand only ever encoded (never decoded, shuffled or checkpointed) works today and would then need an explicitsetCoder(). No Beam code constructs a raw-Messagecoder (grep of main and test code), and the protobuf and GCP IO tests pass with it. It is ready as a separate local commit if wanted.🤖 Generated with Claude Code