Skip to content

refactor: hoist per-operation exceptionMetadataMapper to client field - #7240

Open
zoewangg wants to merge 1 commit into
masterfrom
zoewang/exception-mapper-hoist
Open

refactor: hoist per-operation exceptionMetadataMapper to client field#7240
zoewangg wants to merge 1 commit into
masterfrom
zoewang/exception-mapper-hoist

Conversation

@zoewangg

@zoewangg zoewangg commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Motivation and Context

For the JSON-family protocols (AWS JSON, REST JSON, CBOR, Smithy RPC v2), the code generator emitted a Function<String, Optional<ExceptionMetadata>> exceptionMetadataMapper lambda into every non-streaming operation body of the generated sync and async clients. That lambda is a switch over the service model's exception shapes.

The mapper does not vary per operation: it is built by iterating the whole service model's exception list (model.getShapes() filtered to exceptions), with no operation-specific input. So every operation of a client generated a byte-identical copy of the same switch. For a large JSON client this duplicates a big switch into every operation body, inflating generated source, compiled bytecode, and the packaged jar. It also emits one extra lambda call site per operation, which contributes to the per-class constant pool that large clients accumulate.

This mirrors the metric-lambda reduction in #7226, which hoisted the per-operation metric-publishing lambdas into shared helpers for the same reason.

metric before after delta
generated client .class (sync + async) 3.835 MB 0.670 MB −82.5%
packaged service jar 6.376 MB 5.981 MB −6.2% (−395 KB)
constant-pool entries, sync client 6,442 5,687 −11.7%
constant-pool entries, async client 6,791 6,038 −11.1%

The packaged-jar reduction is much smaller than the raw .class reduction because the duplicated switch compresses very well under the jar's DEFLATE, so the large uncompressed win shrinks once packaged.

Modifications

Hoist the non-streaming exceptionMetadataMapper out of every operation body into a single per-client field, for the four JSON-family protocols, in both the sync and async generated clients.

  • ProtocolSpec gains a default Optional<FieldSpec> errorResponseMapperField() returning empty. Query, REST-XML and EC2 inherit the empty default and are unchanged (they register modeled exceptions on the protocol factory at init time and emit no per-operation mapper).
  • JsonProtocolSpec overrides errorResponseMapperField() to build the whole-model switch once as a private final field, and its errorResponseHandler(...) now emits only the one-line createErrorResponseHandler(..., exceptionMetadataMapper) referencing that field.
  • SyncClientClass / AsyncClientClass add the field to the generated client when the protocol provides one.

Scope and invariants:

  • Non-streaming path only. The per-operation eventstreamExceptionMetadataMapper emitted in event-stream operation bodies is left inline: it iterates that operation's event-stream error members, so it genuinely varies per operation and is not hoistable.
  • Runtime seam unchanged. The mapper still flows through the existing BaseAwsJsonProtocolFactory.createErrorResponseHandler(JsonOperationMetadata, Function) overload to AwsJsonProtocolErrorUnmarshaller exactly as before. The error code is parsed off the wire and looked up in the mapper identically; unmodeled codes still fall back to the default service exception. Behavior is identical for the same error code.
  • No public API change. Default<Svc>[Async]Client is a package-private final class; no public, protected, or SdkInternalApi type changes.

Testing

Updated tests

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Checklist

  • I have read the CONTRIBUTING document
  • Local run of mvn install succeeds
  • My code follows the code style of this project
  • My change requires a change to the Javadoc documentation
  • I have updated the Javadoc documentation accordingly
  • I have added tests to cover my changes
  • All new and existing tests passed
  • I have added a changelog entry. Adding a new entry must be accomplished by running the scripts/new-change script and following the instructions. Commit the new file created by the script in .changes/next-release with your changes.
  • My change is to implement 1.11 parity feature and I have updated LaunchChangelog

License

  • I confirm that this pull request can be released under the Apache 2 license

The JSON-family protocols emitted a byte-identical exceptionMetadataMapper
lambda into every non-streaming operation body. It switches over the whole
service model's exceptions with no per-operation input, so hoist it to a
single per-client field. Removes duplicated bytecode and source without
changing runtime behavior.

Re-seed ClientLambdaCallSiteCeilingTest ceilings for the json/cbor/rpcv2/
aws-json fixtures down to the post-hoist counts, since each non-streaming
operation no longer carries an inline mapper lambda.
@zoewangg
zoewangg requested a review from a team as a code owner August 7, 2026 19:45
@zoewangg
zoewangg requested a review from joviegas August 7, 2026 20:16
@zoewangg zoewangg added the perf-improvement Label for PRs that contain performance improvement changes. label Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

perf-improvement Label for PRs that contain performance improvement changes.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant