Skip to content

Add awsGeneratedTags field to S3Bucket in s3-event-notifications#7138

Draft
ylyu-eng wants to merge 1 commit into
aws:masterfrom
ylyu-eng:feature/en-bucket-system-tags
Draft

Add awsGeneratedTags field to S3Bucket in s3-event-notifications#7138
ylyu-eng wants to merge 1 commit into
aws:masterfrom
ylyu-eng:feature/en-bucket-system-tags

Conversation

@ylyu-eng

@ylyu-eng ylyu-eng commented Jul 13, 2026

Copy link
Copy Markdown

Motivation and Context

Amazon S3 event notifications delivered to SNS, SQS, and Lambda will include a new awsGeneratedTags map on the bucket object when system tags are enabled on the source bucket. The Java SDK model did not round-trip this field, so consumers had to fall back to raw JSON parsing to read the tags. This PR adds first-class support for the field on both the reader and writer sides.

Related: SIM P459766202 (internal launch coordination).

Note to reviewers: This PR targets master for review only. The intent is to retarget to a feature branch once the AWS Java SDK team creates one, and merge on launch day.

Modifications

  • S3Bucket: new Map<String, String> awsGeneratedTags field with getter, a fluent S3Bucket.builder() for constructing instances with the new field, and updates to equals/hashCode/toString. The pre-existing three-argument constructor is preserved so existing callers are source- and behavior-compatible. Tags are defensively copied on construction and returned as an unmodifiable view.
  • DefaultS3EventNotificationReader: parses awsGeneratedTags as Map<String, String> using a LinkedHashMap (preserves S3 emission order). Absent field yields null. Null value entries are preserved. Non-string values throw IllegalArgumentException with a helpful message including the offending key and value.
  • DefaultS3EventNotificationWriter: emits awsGeneratedTags only when the map is non-null and non-empty, matching S3 server-side @JsonInclude(NON_EMPTY) behavior so round-tripped output is indistinguishable from what S3 emits.
  • Bumps eventVersion fixtures in the module test files from 2.4 to 2.5 to reflect the schema version emitted alongside this launch.

Testing

Added tests in three files:

  • S3BucketTest (new): builder + 3-arg constructor defaults, toString behavior (omit-when-null, include-when-present with key/value assertions across multiple tags), defensive-copy of the tag map, and unmodifiable view via the getter.
  • S3EventNotificationReaderTest: parse when present, absent, empty; null value preserved; empty string preserved; non-string value throws; boolean value throws; non-object throws.
  • S3EventNotificationWriterTest: written when set (with round-trip); omitted when null; omitted when empty; null tag value preserved (round-trip); empty-string tag value preserved (round-trip).

Local run of ./mvnw test -pl :s3-event-notifications passes with 34 tests, 0 failures.

Screenshots (if appropriate)

N/A

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 (module-level tests pass)
  • 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.

License

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

@alextwoods alextwoods mentioned this pull request Jul 13, 2026

@alextwoods alextwoods left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looking good!

This also needs a change log - you can create one with ./scripts/new-change

this.name = name;
this.ownerIdentity = ownerIdentity;
this.arn = arn;
this.awsGeneratedTags = awsGeneratedTags;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably do a Collections.unmodifiableMap + defensive copy on this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call-out — my concern is applying it only to awsGeneratedTags would introduce inconsistency with peer collection fields like S3#objectAnnotation which today are stored by reference without a defensive copy.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah - I think it was a miss that objectAnnotation wasn't defensively copied. Defensive copying is the standard pattern across the rest of the SDK when dealing with user provided collections.


@Test
void awsGeneratedTags_whenSet_isWritten() {
Map<String, String> tags = new LinkedHashMap<>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in the reader tests there are tests for null and empty string tag values - we should probably those here as well.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for capturing this, addressing.

this(name, ownerIdentity, arn, null);
}

public S3Bucket(String name, UserIdentity ownerIdentity, String arn, Map<String, String> awsGeneratedTags) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 4 arg constructor here is unfortunately starting to get a little long - our SDK standard practice for cases like this is to @deprecate the 3 arg constructor and introduce a builder.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awsGeneratedTags are populated by AWS services. External SDK callers have no way to generate system tags themselves — they only read getAwsGeneratedTags() off events the SDK has parsed. Deprecating the 3-arg constructor would surface deprecation warnings on legitimate customer code that has no reason to migrate (they can't meaningfully set the new field). That feels like a misleading signal.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah - this is a fair point - I think we can skip adding @deprecated to that constructor, but I do still think introducing the Builder makes sense here rather than a new 4-arg constructor.

@ylyu-eng ylyu-eng force-pushed the feature/en-bucket-system-tags branch 3 times, most recently from 2d9dbdb to e22d9fb Compare July 13, 2026 22:13
Amazon S3 event notifications delivered to SNS, SQS, and Lambda now
include an `awsGeneratedTags` map on the `bucket` object when system
tags are enabled on the source bucket. The Java SDK model did not
round-trip this field, so consumers had to fall back to raw JSON
parsing to read the tags.

This change adds `Map<String, String> awsGeneratedTags` to `S3Bucket`
and threads it through the reader and writer:

  * `S3Bucket`: adds `awsGeneratedTags` field with a getter, a fluent
    `S3Bucket.builder()` for constructing instances with the new
    field, and updates `equals`/`hashCode`/`toString`. The pre-existing
    three-argument constructor is preserved (undeprecated) so existing
    callers are source- and behavior-compatible. Tags are defensively
    copied on construction and returned as an unmodifiable view.
  * `DefaultS3EventNotificationReader`: parses `awsGeneratedTags` as
    `Map<String, String>`. Absent field yields `null`. Null value
    entries are preserved. Non-string values throw
    `IllegalArgumentException` with a helpful message including the
    offending key and value.
  * `DefaultS3EventNotificationWriter`: emits `awsGeneratedTags` only
    when the map is non-null and non-empty, matching S3's server-side
    `@JsonInclude(NON_EMPTY)` behavior so round-tripped output is
    indistinguishable from what S3 emits.

Also bumps `eventVersion` fixtures in the module test files from
`2.4` to `2.5` to reflect the schema version emitted alongside this
launch.

The change is fully additive:
  * Older SDKs remain compatible with new payloads (unknown fields
    are ignored during parse).
  * Newer SDKs remain compatible with old payloads
    (`getAwsGeneratedTags()` returns `null` when the field is absent).
@ylyu-eng ylyu-eng force-pushed the feature/en-bucket-system-tags branch from e22d9fb to ee45a15 Compare July 13, 2026 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants