Skip to content

Cap ServiceBusMessageBatch size at 1 MB to match broker enforcement#48214

Open
EldertGrootenboer wants to merge 1 commit intoAzure:mainfrom
EldertGrootenboer:fix/servicebus-batch-size-cap-ICM51000000793879
Open

Cap ServiceBusMessageBatch size at 1 MB to match broker enforcement#48214
EldertGrootenboer wants to merge 1 commit intoAzure:mainfrom
EldertGrootenboer:fix/servicebus-batch-size-cap-ICM51000000793879

Conversation

@EldertGrootenboer
Copy link
Contributor

@EldertGrootenboer EldertGrootenboer commented Mar 4, 2026

Description

The Service Bus broker enforces a 1 MB batch size limit regardless of the max-message-size advertised on the AMQP link. On Premium partitioned namespaces, the gateway advertises 100 MB on the link, causing tryAddMessage() to accept batches up to 100 MB that the broker will then reject.

Root Cause

The AMQP gateway sets max-message-size on the link based on namespace tier:

  • Standard: 1 MB (matches broker limit)
  • Premium non-partitioned: per-entity value (e.g., 2 MB)
  • Premium partitioned: 100 MB (hardcoded in gateway)

The broker always enforces 1 MB for batches (maxMessageSize = 1048576 from deployed config). The vendor property com.microsoft:max-message-batch-size echoes max-message-size (always equal), so reading it does not help.

This is a known service-side gap tracked by azure-service-bus#708. The .NET SDK applies the same 1 MB cap (azure-sdk-for-net#44914).

Fix

Add MAX_BATCH_SIZE_BYTES = 1024 * 1024 (1 MB) with a single enforcement point in createMessageBatch():

final int maximumLinkSize = Math.min(size > 0 ? size : MAX_MESSAGE_LENGTH_BYTES, MAX_BATCH_SIZE_BYTES);

Capped paths (go through createMessageBatch()):

  • createMessageBatch() / createMessageBatch(options) -- explicit batch creation
  • sendMessages(Iterable) -- internally calls createMessageBatch() per batch
  • scheduleMessages(Iterable) -- internally calls createMessageBatch() for all messages

Uncapped paths (intentionally bypass batch cap):

  • sendMessage(single) -- goes through sendFluxInternal -> AmqpMessageCollector using raw link size
  • scheduleMessage(single) -- goes through scheduleMessageInternal using raw link size

Single-message paths are intentionally uncapped since individual messages on Premium can validly exceed 1 MB up to the per-entity limit.

When a user requests a batch size exceeding 1 MB via CreateMessageBatchOptions, a ServiceBusException is thrown (consistent with the existing behavior when exceeding the link size).

Testing

8 new parameterized unit tests (x2 V1/V2 = 16 executions):

  • createBatchCappedAtMaxBatchSizeWhenLinkReportsLargerSize -- 100 MB link -> batch capped at 1 MB
  • createBatchUsesLinkSizeWhenSmallerThanMaxBatchSize -- 256 KB link -> uses link size
  • createBatchWithOptionsExceedingMaxBatchSizeCapThrowsError -- user requests 2 MB -> error
  • createBatchWithOptionsSmallerThanMaxBatchSizeCapIsRespected -- user requests 500 KB -> respected
  • sendMessagesIterableWithLargeLinkCapsAt1MB -- iterable path uses capped batch
  • sendSingleMessageNotCappedWithLargeLink -- single send path NOT capped
  • scheduleMessagesIterableWithLargeLinkCapsAt1MB -- schedule iterable uses capped batch
  • scheduleMessageSingleNotCappedWithLargeLink -- single schedule NOT capped

4 additional asymmetry-focused tests (x2 V1/V2 = 8 executions):

  • sendSingleMessageLargerThan1MBSucceedsWithLargeLink -- 2 MB message succeeds via single send
  • sendMessagesIterableRejectsSingleMessageLargerThan1MBOnLargeLink -- same 2 MB message fails via iterable
  • scheduleMessagesIterableRejectsSingleMessageLargerThan1MBOnLargeLink -- same for schedule iterable
  • createBatchFallbackWhenLinkReportsZeroSize -- link size 0 -> fallback to 256 KB

All 965 tests pass (0 failures, 0 errors). Spotless and checkstyle clean.

ICM Reference

ICM 51000000793879

Copilot AI review requested due to automatic review settings March 4, 2026 00:46
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the Service Bus Java sender batching logic to enforce a hard 1 MB maximum batch size, aligning client-side behavior with broker enforcement when AMQP link max-message-size is overstated (notably Premium partitioned namespaces).

Changes:

  • Introduces a 1 MB cap constant and applies it when computing effective batch sizing across batch creation and send/schedule pathways.
  • Adds parameterized unit tests validating capping behavior when the link reports sizes above/below 1 MB and when user-specified batch options exceed the cap.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSenderAsyncClient.java Adds MAX_BATCH_SIZE_BYTES and caps link-derived sizing in batch creation, scheduling, flux send batching, and the message collector.
sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSenderAsyncClientTest.java Adds parameterized tests to validate 1 MB cap behavior and option validation under both V1/V2 stacks.

Comment on lines 474 to 477
new IllegalArgumentException(String.format(Locale.US,
"CreateMessageBatchOptions.getMaximumSizeInBytes (%s bytes) is larger than the link size"
+ " (%s bytes).",
maxSize, maximumLinkSize)));
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

The new 1 MB cap means maximumLinkSize may no longer be the AMQP link size, but the exception message still says the requested size is "larger than the link size". This will be misleading when the link reports (e.g.) 100 MB but the effective limit is capped to 1 MB. Consider updating the message text to describe the effective maximum (and optionally include both the link-reported size and the 1 MB cap).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Updated the error message from "larger than the link size" to "larger than the maximum allowed size" since the effective limit may be the 1 MB batch cap rather than the AMQP link's reported size.

Comment on lines +225 to +227
// Temporary workaround: Service Bus enforces a maximum batch size of 1 MB that is not
// communicated via the AMQP link's max-message-size property. The link reports the per-message
// limit (up to 100 MB for Premium partitioned), but batches are rejected above 1 MB.
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

The comment describes this as a "maximum batch size" workaround, but the constant is also used as a cap for maxSendLinkSize when scheduling messages (which is a per-message encoding limit in ManagementChannel.schedule). Consider adjusting the comment (and/or constant name) to reflect that this is an effective broker-enforced max size applied to link size for multiple code paths, not just batching.

Suggested change
// Temporary workaround: Service Bus enforces a maximum batch size of 1 MB that is not
// communicated via the AMQP link's max-message-size property. The link reports the per-message
// limit (up to 100 MB for Premium partitioned), but batches are rejected above 1 MB.
// Temporary workaround: Service Bus enforces an effective maximum message payload size of 1 MB
// that is not communicated via the AMQP link's max-message-size property. This limit is applied
// to the link size in multiple code paths, including batch sends and scheduling (per-message
// encoding via ManagementChannel.schedule), even though the link may report a higher
// per-message limit (up to 100 MB for Premium partitioned).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good observation. On closer analysis, the 1 MB limit is specifically for batch sends - individual messages on Premium namespaces can validly exceed 1 MB. The scheduleMessageInternal path sends a single message through the management channel and should NOT be capped at 1 MB (that would break Premium users scheduling large individual messages). scheduleMessages (plural) already goes through createMessageBatch() which has the cap.

Changes made:

  1. Removed the 1 MB cap from scheduleMessageInternal (single-message path)
  2. Updated the comment to clarify this is batch-specific and document which paths are/aren't capped
  3. Kept MAX_BATCH_SIZE_BYTES name since the constant is batch-specific

@EldertGrootenboer EldertGrootenboer force-pushed the fix/servicebus-batch-size-cap-ICM51000000793879 branch from 40a0fac to 2cb1714 Compare March 4, 2026 01:02
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment on lines 895 to 899
= getSendLinkWithRetry("send-batches").flatMap(link -> link.getLinkSize().flatMap(size -> {
final int batchSize = size > 0 ? size : MAX_MESSAGE_LENGTH_BYTES;
final int batchSize = Math.min(size > 0 ? size : MAX_MESSAGE_LENGTH_BYTES, MAX_BATCH_SIZE_BYTES);
final CreateMessageBatchOptions batchOptions
= new CreateMessageBatchOptions().setMaximumSizeInBytes(batchSize);
return messages.collect(
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

sendFluxInternal() now caps batchSize to MAX_BATCH_SIZE_BYTES. Since both sendMessage(...) overloads call sendFluxInternal(Flux.just(message), ...), this change also caps single-message sends to 1 MB (because the message must fit into the ServiceBusMessageBatch used by AmqpMessageCollector). That contradicts the nearby comment stating only batch-sending paths are capped and will break sending individual messages larger than 1 MB when the link reports a higher per-message limit (e.g., Premium non-partitioned entities > 1 MB). Consider bypassing the batch-size cap for the single-message send path (e.g., send directly via the link) or otherwise allowing an oversized single message to be sent as a single transfer while still capping true AMQP batch transfers. Add a unit test that verifies a >1 MB single message is allowed when link size permits it.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed in the current code. The sendFluxInternal path no longer applies the 1 MB cap. The cap is enforced exclusively in createMessageBatch(), which means:

  • sendMessage(single) uses the raw link size (via sendFluxInternalAmqpMessageCollector) — single messages > 1 MB are allowed on Premium
  • sendMessages(Iterable) uses createMessageBatch() which caps at 1 MB
  • scheduleMessage(single) uses the raw link size (scheduleMessageInternal)
  • scheduleMessages(Iterable) uses createMessageBatch() which caps at 1 MB

Added comprehensive tests proving this asymmetry, including tests with actual > 1 MB messages that succeed on the uncapped paths and fail on the capped paths.

Comment on lines +228 to +232
// This cap is applied only to batch-sending code paths (createMessageBatch, sendFlux,
// AmqpMessageCollector). Single-message paths (e.g. scheduleMessage) use the link-reported
// per-message limit, which is valid for individual messages on Premium namespaces.
// Tracked by: https://github.com/Azure/azure-service-bus/issues/708
static final int MAX_BATCH_SIZE_BYTES = 1024 * 1024;
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

The PR description says scheduleMessageInternal() is capped to 1 MB, but the current implementation does not apply MAX_BATCH_SIZE_BYTES to the max size passed to managementNode.schedule(...) (it uses the link-reported size or MAX_MESSAGE_LENGTH_BYTES). Please update the PR description (or the code, if the description is the intended behavior) so they match.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. The PR description was stale from an earlier iteration that capped all four paths. Updated the description to accurately reflect the single enforcement point design.

@EldertGrootenboer EldertGrootenboer force-pushed the fix/servicebus-batch-size-cap-ICM51000000793879 branch from 2cb1714 to 8de6ada Compare March 4, 2026 16:01
The Service Bus broker enforces a 1 MB batch size limit regardless of the
max-message-size advertised on the AMQP link. Premium partitioned namespaces
advertise 100 MB on the link, causing tryAddMessage() to accept batches the
broker will reject.

Cap batch creation in ServiceBusSenderAsyncClient.createMessageBatch() at
1 MB (MAX_BATCH_SIZE_BYTES). This is the single enforcement point: both
sendMessages(iterable) and scheduleMessages(iterable) call createMessageBatch
internally. Single-message paths (sendMessage, scheduleMessage) are NOT
capped since the 1 MB limit is batch-specific and individual messages on
Premium can validly exceed 1 MB up to the per-entity limit.

When a user requests a batch size exceeding 1 MB via
CreateMessageBatchOptions, throw ServiceBusException.

Tracking: azure-service-bus#708
ICM: 51000000793879
@EldertGrootenboer EldertGrootenboer force-pushed the fix/servicebus-batch-size-cap-ICM51000000793879 branch from 8de6ada to e3b2ef6 Compare March 4, 2026 16:56
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants