Skip to content

Add container block support - #462

Merged
yyakhnytsia merged 4 commits into
HubSpot:masterfrom
DeirdreCleary:decleary/add-container-block-support
Jul 27, 2026
Merged

Add container block support#462
yyakhnytsia merged 4 commits into
HubSpot:masterfrom
DeirdreCleary:decleary/add-container-block-support

Conversation

@DeirdreCleary

Copy link
Copy Markdown

Why

The Slack Block Kit API introduced an official container block type. This adds support for deserializing and building container blocks in this library, following the same patterns as other recently-added block types (card, task_card, data_table, table).

What changed

  • ContainerBlockIF / ContainerBlock — Immutables implementation of the container block with all Slack API fields: required title (Text) and child_blocks (ImmutableList); optional subtitle, icon (Image element), width, is_collapsible, default_collapsed, and block_id
  • ContainerBlockWidth — enum for the width field values (narrow, standard, wide, full)
  • BlockElementLengthLimits — added MAX_CONTAINER_CHILD_BLOCKS(10)
  • Block.java — registered ContainerBlock in @JsonSubTypes
  • Uses @JsonInclude(NON_ABSENT) (not NON_EMPTY) on optional fields, per pattern established in PR Add card block support #456 to avoid Slack returning invalid_arguments

Validation

@Check enforces:

  • title text ≤ 150 characters
  • subtitle text ≤ 150 characters (when present)
  • child_blocks count ≤ 10

Test plan

  • ContainerBlockTest covers deserialization of all fields, minimal block, round-trip serialization, and all three validation failure cases
  • mvn test passes locally for the slack-base module

BRAVE

  • Backwards compatible — adding a new @JsonSubTypes.Type entry is additive; existing code deserializing blocks is unaffected
  • Rollout/Rollback — library change, no deploy; consumers upgrade at their own pace
  • Automated testing — 7 unit tests added
  • Verification — build passed locally
  • Expect failures — none expected in existing consumers; new type is purely additive

The Slack Block Kit API added a container block type
(https://docs.slack.dev/reference/block-kit/blocks/container-block).
This adds ContainerBlockIF/ContainerBlock following the same Immutables
+ @JsonSubTypes pattern used by all other block types, including
validation for title/subtitle length (≤150 chars) and child_blocks
count (≤10).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@DeirdreCleary
DeirdreCleary marked this pull request as ready for review July 17, 2026 10:28

@yyakhnytsia yyakhnytsia 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.

reviewed, left few comments.

return TYPE;
}

Text getTitle();

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.

According to the documentation title is optional:

Optional (one of title or rich_text_title is required)

Also, it would be nice to have the other optional fields from the Slack documentation to have the full functionality of this component.

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 point! title is now Optional<Text>, and I've added rich_text_title (Optional<RichTextBlock>) with a @Check validation that at least one of the two is present.

I also went through the full spec and added the remaining optional fields: subtitle, icon, width, is_collapsible, default_collapse and has_header_divider.


Text getTitle();

ImmutableList<Block> getChildBlocks();

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.

Could you please check the supported type blocks: https://docs.slack.dev/reference/block-kit/blocks/container-block/#child-blocks
The used block has more types than are supported by the container type.
Also if we use the Block component it is possible to use recursive blocks: ContainerBlock inside ContainerBlock as a child component; not sure if such a structure is allowed.

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 flagging this. I've looked at a few approaches for constraining child_blocks:

Option A — dedicated union interface (ContainerChildBlock) following the BlockElement/RichTextObject pattern. This enforces the constraint at compile time, but requires every supported block type’s IF interface to extend ContainerChildBlock, and both Block.java and ContainerChildBlock.java would need updating whenever a new block type is added. My concern is that this would become a maintenance burden over time.

Option B — allowlist in @Check using a Set<String> of the allowed TYPE constants (e.g. Actions.TYPE, Divider.TYPE, etc.), defined in ContainerBlockIF. The constraint lives in one place, unknown types (future Slack block types that deserialize as UnknownBlock) pass through for forward compatibility, and disallowed known types (e.g. card) are caught at build time with a clear error.

I'm inclined to go with Option B, but wanted to check your thoughts.

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.

If you have concerns about option A, then option B is also the case. For me, Option A is better for development, since we have a clear restriction.

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.

Updated with A


@Check
default void check() {
Preconditions.checkState(

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.

Text type validation is missing:

Title of the container, using plain_text formatting

We have two types of Text: plain_text and Markdown

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.

Fixed. We now validate in @Check that when title is present, its TextType is PLAIN_TEXT — building with a mrkdwn title throws IllegalStateException: title must use plain_text formatting.

decleary and others added 3 commits July 23, 2026 15:51
- title is now Optional (one of title or rich_text_title is required)
- Add rich_text_title field (RichTextBlock)
- Add has_header_divider field
- Validate title type must be plain_text
- Validate at least one of title/rich_text_title is present
- Prevent recursive container blocks in child_blocks

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- default_collapsed requires is_collapsible to be true
- has_header_divider cannot be set on collapsible blocks

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Introduces ContainerChildBlock, a @JsonSubTypes-registered union interface
following the BlockElement/RichTextObject pattern, so that child_blocks
is typed as ImmutableList<ContainerChildBlock> rather than the unconstrained
ImmutableList<Block>. Supported types (actions, context, divider, file,
header, image, input, rich_text, section, table) implement the interface;
UnknownBlock is the defaultImpl for forward compatibility with future
Slack block types.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

@yyakhnytsia yyakhnytsia 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.

LGTM.

@yyakhnytsia yyakhnytsia 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.

LGTM.

@yyakhnytsia
yyakhnytsia merged commit e0e6ad3 into HubSpot:master Jul 27, 2026
1 check passed
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