Skip to content

Latest commit

 

History

History
660 lines (469 loc) · 32.4 KB

File metadata and controls

660 lines (469 loc) · 32.4 KB

AGENTS.md - java-slack-sdk

Project Overview

This is the Slack SDK for Java (com.slack.api), a multi-module Maven project providing Java/JVM libraries for building Slack apps and calling Slack APIs. The repository is at https://github.com/slackapi/java-slack-sdk and the documentation lives at https://docs.slack.dev/tools/java-slack-sdk.

There are two main components:

  • Bolt for Java: A framework for building modern Slack apps with a simple listener-based API
  • Slack API Client: Low-level HTTP clients for all Slack APIs (Web API, Socket Mode, SCIM, Audit Logs)

Build System

  • Build tool: Apache Maven via Maven Wrapper (./mvnw)
  • Java target: Source and target compatibility is Java 1.8 (OpenJDK 8+) — see pom.xml properties maven.compiler.source and maven.compiler.target
  • Group ID: com.slack.api — see pom.xml <groupId>
  • Current version: See <version> in root pom.xml (also reflected in *LibraryVersion.java files generated by scripts/set_version.sh)

Key Commands

# Run CI tests (local-only tests, no Slack API access needed)
./scripts/run_no_prep_tests.sh

# Install all modules locally (skips tests)
./scripts/install_local.sh

# Run all tests (requires Slack API tokens - see .github/maintainers_guide.md)
./mvnw test

# Check for dependency updates
./scripts/check_dependency_updates.sh

# Check for duplicate classes in JARs
./mvnw install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true && ./mvnw duplicate-finder:check

# Set project version across all modules
./scripts/set_version.sh <version>  # e.g., 1.48.0 or 1.48.0-SNAPSHOT

CI Tests

CI runs ./scripts/run_no_prep_tests.sh -ci across JDK 8, 14, and 17. Only tests in the test_locally package are executed in CI:

-Dtest=test_locally.**.*Test

Some modules are excluded on JDK 8 (Jakarta EE modules, GCF, Helidon, http4k, Micronaut). JDK 17 runs the full suite.

Module Architecture

All published modules use group ID com.slack.api (see pom.xml <groupId>) and are released to Maven Central simultaneously. The complete list of modules is defined in root pom.xml <modules> section.

Foundation Modules

  • slack-api-model — A collection of the classes representing the Slack core objects such as conversations, messages, users, blocks, and surfaces. All other modules depend on this. Maven Central

  • slack-api-client — The official Slack API client for Java. Provides HTTP clients for Web API, Socket Mode, SCIM, Audit Logs, and Status API. Maven Central

  • slack-app-backend — A set of Slack app server-side handlers and data classes for Events API, Interactive Components, Slash Commands, Actions, and OAuth flow. Maven Central

  • slack-jakarta-socket-mode-client — An option to switch to Jakarta EE compatible Socket Mode client. Maven Central

Kotlin Extensions

  • slack-api-model-kotlin-extension — Contains the Block Kit Kotlin DSL builder, which allows you to define block kit structures via a Kotlin-native DSL. Maven Central

  • slack-api-client-kotlin-extension — Contains extension methods for various Slack client message builders so you can seamlessly use the Block Kit Kotlin DSL directly on the Java message builders. Maven Central

Bolt Framework

  • bolt — The official Slack Bolt framework for Java, used for building modern Slack apps. Maven Central

Bolt Socket Mode Adapters

  • bolt-socket-mode — A handy way to run Slack Bolt apps through Socket Mode connections. Maven Central

  • bolt-jakarta-socket-mode — Provides WebSocket connectivity to the Slack API using Socket Mode, compatible with Jakarta EE. Maven Central

Bolt Servlet Adapters

  • bolt-servlet — A handy way to run Slack Bolt apps on the Java EE Servlet environments. Maven Central

  • bolt-jakarta-servlet — A handy way to run Slack Bolt apps on the Jakarta EE Servlet environments. Maven Central

Bolt HTTP Server Adapters

  • bolt-jetty — A handy way to run Slack Bolt apps on the Java EE compatible Jetty HTTP server. Maven Central

  • bolt-jakarta-jetty — A handy way to run Slack Bolt apps on Jakarta EE compatible Jetty HTTP server. Maven Central

Bolt Serverless Adapters

  • bolt-aws-lambda — An adapter for running Slack Bolt for Java applications on AWS Lambda. Maven Central

  • bolt-google-cloud-functions — An adapter for running Slack Bolt for Java applications on Google Cloud Functions. Requires Java 11+. Maven Central

Bolt Framework Adapters

  • bolt-micronaut — An adapter for running Slack Bolt for Java applications on Micronaut. Requires Java 17+. Maven Central

  • bolt-ktor — An adapter for running Slack Bolt for Java applications on Ktor. Maven Central

  • bolt-http4k — An adapter for running Slack Bolt for Java applications on http4k. Requires Java 11+. Maven Central

  • bolt-helidon — An adapter for running Slack Bolt for Java applications on Helidon. Requires Java 11+. Maven Central

Example Modules (not published to Maven Central)

bolt-kotlin-examples, bolt-quarkus-examples, bolt-spring-boot-examples, bolt-docker-examples, bolt-google-cloud-functions-example

Key Dependencies

All dependency versions are managed in root pom.xml <properties> section.

Library Version Property Notes
OkHttp okhttp.version HTTP client
Gson gson.version JSON serialization; uses LOWER_CASE_WITH_UNDERSCORES field naming via GsonFactory.createSnakeCase()
SLF4J slf4j.version Logging facade. Do not upgrade to v2 - see issue #1034
Lombok lombok.version Used extensively for @Data, @Builder, @Slf4j
Kotlin kotlin.version Compiler version is 1.9.x; apiVersion and languageVersion pinned to 1.6 for backward compat
JUnit junit.version JUnit 4, not 5
Mockito mockito-core.version Capped below v5 for Java 8 compatibility
Hamcrest hamcrest.version Used for test assertions

Note: Always check pom.xml <properties> for actual current versions.

Code Conventions

Java Style

  • Java 8 compatibility is mandatory (see pom.xml maven.compiler.source and maven.compiler.target = 1.8). Do not use Java 9+ APIs (e.g., List.of(), Map.of(), var, HttpClient, text blocks).
  • Lombok annotations are used pervasively: @Data, @Builder, @Slf4j, @EqualsAndHashCode(callSuper = false).
  • JSON field naming uses snake_case via Gson's LOWER_CASE_WITH_UNDERSCORES policy (see slack-api-client/src/main/java/com/slack/api/util/json/GsonFactory.java). Java fields are camelCase.
  • Logging uses SLF4J via Lombok's @Slf4j (gives you a log field).
  • Checked exceptions: API methods throw IOException and SlackApiException.

Naming Conventions

  • Request classes: {MethodName}Request with @Data @Builder and implementing SlackApiRequest (see slack-api-client/src/main/java/com/slack/api/methods/SlackApiRequest.java)
  • Response classes: {MethodName}Response with @Data and implementing SlackApiTextResponse (see slack-api-client/src/main/java/com/slack/api/methods/SlackApiTextResponse.java)
  • API method constants: Methods.java has public static final String for each API method (e.g., USERS_INFO = "users.info") — see slack-api-client/src/main/java/com/slack/api/methods/Methods.java
  • Test packages: test_locally for CI-safe tests, test_with_remote_apis for integration tests needing tokens

JSON Serialization

  • Use GsonFactory.createSnakeCase() for most Slack APIs (snake_case JSON keys with LOWER_CASE_WITH_UNDERSCORES policy)
  • Use GsonFactory.createCamelCase() for SCIM APIs
  • The factory implementation is in slack-api-client/src/main/java/com/slack/api/util/json/GsonFactory.java

Model Types (slack-api-model)

The slack-api-model module contains all core data types. When adding or modifying model objects, follow these patterns.

Block Kit Elements

Directory: slack-api-model/src/main/java/com/slack/api/model/block/element/

All block elements extend BlockElement and use a TYPE constant:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class DatePickerElement extends BlockElement {
    public static final String TYPE = "datepicker";
    private final String type = TYPE;

    private String actionId;
    private PlainTextObject placeholder;
    private String initialDate;
    private ConfirmationDialogObject confirm;
    private Boolean focusOnLoad;
}

Event Types

Directory: slack-api-model/src/main/java/com/slack/api/model/event/

Events implement the Event interface. Nested objects use inner @Data classes:

@Data
public class ChannelCreatedEvent implements Event {
    public static final String TYPE_NAME = "channel_created";
    private final String type = TYPE_NAME;
    private Channel channel;

    @Data
    public static class Channel {
        private String id;
        private String name;
        // ...
    }
}

Key conventions for model types

  • Use @Data, @Builder, @NoArgsConstructor, @AllArgsConstructor, @EqualsAndHashCode(callSuper = false) on concrete types.
  • Define public static final String TYPE (or TYPE_NAME) and private final String type = TYPE;.
  • Java fields are camelCase; Gson's LOWER_CASE_WITH_UNDERSCORES policy (see GsonFactory.java) maps them to snake_case JSON automatically.
  • Use @SerializedName only when the Slack API field name does not follow snake_case convention.
  • Do not initialize List fields to empty collections — Slack sometimes returns errors with empty arrays, and null vs empty has semantic meaning.

Adding a New Slack API Method

When Slack adds a new API method, follow this pattern. The examples below are based on a real PR that added conversations.requestSharedInvite.approve and conversations.requestSharedInvite.deny. Substitute the real method name, request fields, and response fields as needed.

1. Look up the API method documentation

Check the Slack API reference at https://docs.slack.dev/reference/methods/{method.name} to understand the method's request parameters, response fields, and rate limit tier. This is the source of truth for field names and types.

2. Add the method constant to Methods.java

File: slack-api-client/src/main/java/com/slack/api/methods/Methods.java

Add a public static final String constant. The constant name is the API method name in UPPER_SNAKE_CASE with dots replaced by underscores:

public static final String CONVERSATIONS_REQUEST_SHARED_INVITE_APPROVE = "conversations.requestSharedInvite.approve";
public static final String CONVERSATIONS_REQUEST_SHARED_INVITE_DENY = "conversations.requestSharedInvite.deny";

3. Create the Request class

File: slack-api-client/src/main/java/com/slack/api/methods/request/conversations/request_shared_invite/ConversationsRequestSharedInviteApproveRequest.java

The package path mirrors the API namespace (dots become directory separators, camelCase segments become snake_case). The class implements SlackApiRequest and uses @Data @Builder:

package com.slack.api.methods.request.conversations.request_shared_invite;

import com.slack.api.methods.SlackApiRequest;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * https://docs.slack.dev/reference/methods/conversations.requestSharedInvite.approve
 */
@Data
@Builder
public class ConversationsRequestSharedInviteApproveRequest implements SlackApiRequest {

    private String token;

    /**
     * ID of the requested shared channel invite to approve.
     */
    private String inviteId;

    /**
     * Optional channel_id to which external user will be invited to.
     * Will override the value on the requested invite.
     */
    private String channelId;

    /**
     * Optional boolean on whether the invited team will have post-only permissions in the channel.
     * Will override the value on the requested invite.
     */
    private Boolean isExternalLimited;

    /**
     * Object describing the text to send along with the invite.
     */
    private Message message;

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Message {
        private String text;
        private boolean isOverride;
    }
}

For simpler methods without nested objects, the same pattern applies but without the inner @Data classes (e.g., ConversationsRequestSharedInviteDenyRequest has only token, inviteId, and message fields).

4. Create the Response class

File: slack-api-client/src/main/java/com/slack/api/methods/response/conversations/request_shared_invite/ConversationsRequestSharedInviteApproveResponse.java

The response class implements SlackApiTextResponse and uses @Data. Every response must include the standard fields defined by the SlackApiTextResponse interface (ok, warning, error, needed, provided, httpResponseHeaders). Add any method-specific response fields from the API docs:

package com.slack.api.methods.response.conversations.request_shared_invite;

import com.slack.api.methods.SlackApiTextResponse;
import lombok.Data;

import java.util.List;
import java.util.Map;

@Data
public class ConversationsRequestSharedInviteApproveResponse implements SlackApiTextResponse {

    private boolean ok;
    private String warning;
    private String error;
    private String needed;
    private String provided;
    private transient Map<String, List<String>> httpResponseHeaders;

    private String inviteId;
}

5. Add to MethodsClient interface

File: slack-api-client/src/main/java/com/slack/api/methods/MethodsClient.java

Every method needs two overloads — one accepting the Request object, one accepting a RequestConfigurator lambda:

ConversationsRequestSharedInviteApproveResponse conversationsRequestSharedInviteApprove(
        ConversationsRequestSharedInviteApproveRequest req) throws IOException, SlackApiException;

ConversationsRequestSharedInviteApproveResponse conversationsRequestSharedInviteApprove(
        RequestConfigurator<ConversationsRequestSharedInviteApproveRequest.ConversationsRequestSharedInviteApproveRequestBuilder> req)
        throws IOException, SlackApiException;

6. Add to AsyncMethodsClient interface

File: slack-api-client/src/main/java/com/slack/api/methods/AsyncMethodsClient.java

Same two overloads, but returning CompletableFuture:

CompletableFuture<ConversationsRequestSharedInviteApproveResponse> conversationsRequestSharedInviteApprove(
        ConversationsRequestSharedInviteApproveRequest req);

CompletableFuture<ConversationsRequestSharedInviteApproveResponse> conversationsRequestSharedInviteApprove(
        RequestConfigurator<ConversationsRequestSharedInviteApproveRequest.ConversationsRequestSharedInviteApproveRequestBuilder> req);

7. Implement in MethodsClientImpl

File: slack-api-client/src/main/java/com/slack/api/methods/impl/MethodsClientImpl.java

The sync implementation uses postFormWithTokenAndParseResponse to send the form, authenticate with the token, and deserialize the response:

@Override
public ConversationsRequestSharedInviteApproveResponse conversationsRequestSharedInviteApprove(
        ConversationsRequestSharedInviteApproveRequest req) throws IOException, SlackApiException {
    return postFormWithTokenAndParseResponse(toForm(req),
            Methods.CONVERSATIONS_REQUEST_SHARED_INVITE_APPROVE, getToken(req),
            ConversationsRequestSharedInviteApproveResponse.class);
}

@Override
public ConversationsRequestSharedInviteApproveResponse conversationsRequestSharedInviteApprove(
        RequestConfigurator<ConversationsRequestSharedInviteApproveRequest.ConversationsRequestSharedInviteApproveRequestBuilder> req)
        throws IOException, SlackApiException {
    return conversationsRequestSharedInviteApprove(
            req.configure(ConversationsRequestSharedInviteApproveRequest.builder()).build());
}

8. Implement in AsyncMethodsClientImpl

File: slack-api-client/src/main/java/com/slack/api/methods/impl/AsyncMethodsClientImpl.java

The async implementation delegates to the sync MethodsClientImpl via an executor:

@Override
public CompletableFuture<ConversationsRequestSharedInviteApproveResponse> conversationsRequestSharedInviteApprove(
        ConversationsRequestSharedInviteApproveRequest req) {
    return executor.execute(CONVERSATIONS_REQUEST_SHARED_INVITE_APPROVE, toMap(req),
            () -> methods.conversationsRequestSharedInviteApprove(req));
}

@Override
public CompletableFuture<ConversationsRequestSharedInviteApproveResponse> conversationsRequestSharedInviteApprove(
        RequestConfigurator<ConversationsRequestSharedInviteApproveRequest.ConversationsRequestSharedInviteApproveRequestBuilder> req) {
    return conversationsRequestSharedInviteApprove(
            req.configure(ConversationsRequestSharedInviteApproveRequest.builder()).build());
}

9. Add form builder support in RequestFormBuilder

File: slack-api-client/src/main/java/com/slack/api/methods/RequestFormBuilder.java

Add a toForm method that maps request fields to form parameters. Use setIfNotNull for simple fields. For complex nested objects, serialize to JSON with GSON.toJson():

public static FormBody.Builder toForm(ConversationsRequestSharedInviteApproveRequest req) {
    FormBody.Builder form = new FormBody.Builder();
    setIfNotNull("invite_id", req.getInviteId(), form);
    setIfNotNull("channel_id", req.getChannelId(), form);
    setIfNotNull("is_external_limited", req.getIsExternalLimited(), form);
    if (req.getMessage() != null) {
        setIfNotNull("message", GSON.toJson(req.getMessage()), form);
    }
    return form;
}

public static FormBody.Builder toForm(ConversationsRequestSharedInviteDenyRequest req) {
    FormBody.Builder form = new FormBody.Builder();
    setIfNotNull("invite_id", req.getInviteId(), form);
    setIfNotNull("message", req.getMessage(), form);
    return form;
}

10. Add rate limit tier in MethodsRateLimits

File: slack-api-client/src/main/java/com/slack/api/methods/MethodsRateLimits.java

Look up the rate limit tier from the API docs (or check metadata/web-api/rate_limit_tiers.json for existing patterns) and add a setRateLimitTier call in the appropriate alphabetical position:

setRateLimitTier(CONVERSATIONS_REQUEST_SHARED_INVITE_APPROVE, Tier2);
setRateLimitTier(CONVERSATIONS_REQUEST_SHARED_INVITE_DENY, Tier2);

11. Add rate limit entry in rate_limit_tiers.json

File: metadata/web-api/rate_limit_tiers.json

Add the method name and its tier in alphabetical order within the JSON object. This file is the source of truth for rate limit tiers and should match the tier set in MethodsRateLimits.java:

"conversations.requestSharedInvite.approve": "Tier2",
"conversations.requestSharedInvite.deny": "Tier2",

12. Check mock server JSON fixture

File (new): json-logs/samples/api/{method.name}.json

The MockSlackApiServer used in tests serves mock responses from JSON files in this directory. Every API method must have a corresponding JSON fixture file, or tests will fail with NoSuchFileException / HTTP 500. The file name is the full API method name with dots (e.g., conversations.requestSharedInvite.approve.json).

The mock server reads these files and replaces "ok": false, with "ok": true, automatically, so the fixture should always have "ok": false. See existing files in this directory for examples:

{
  "ok": false,
  "error": "",
  "needed": "",
  "provided": "",
  "warning": ""
}

13. Add tests and update MethodsTest

Add method-specific tests

File: slack-api-client/src/test/java/test_locally/api/methods/{Category}Test.java (the test file matching the top-level API namespace, e.g., AppsTest.java for apps.*, ConversationsTest.java for conversations.*)

Test both sync and async variants using the configurator lambda pattern:

@Test
public void conversationsRequestSharedInviteApprove() throws Exception {
    assertThat(slack.methods(ValidToken).conversationsRequestSharedInviteApprove(r -> r)
            .isOk(), is(true));
}

@Test
public void conversationsRequestSharedInviteApprove_async() throws Exception {
    assertThat(slack.methodsAsync(ValidToken).conversationsRequestSharedInviteApprove(r -> r)
            .get().isOk(), is(true));
}

Update endpoint coverage in MethodsTest

File: slack-api-client/src/test/java/test_locally/api/MethodsTest.java

This test validates that every known Slack API method has a corresponding constant in Methods.java and rate limit entry in metadata/web-api/rate_limit_tiers.json. Update the methods string and the endpoint count comment to include the new method names. If the method names are not already in the list, add them in the correct alphabetical position.

14. Add imports to all modified files

Each file that references the new Request and Response classes needs corresponding import statements. When modifying the following files, add imports for both the new Request and Response classes:

  • MethodsClient.java — import both request and response classes
  • AsyncMethodsClient.java — import both request and response classes
  • MethodsClientImpl.java — import both request and response classes
  • AsyncMethodsClientImpl.java — import both request and response classes
  • RequestFormBuilder.java — import the request class only

Place each new import adjacent to the existing imports from the same apps.* (or equivalent) package, in alphabetical order.

15. Verify

Before considering the work complete, run the tests. The slack-api-client module depends on slack-api-model, so you must install all modules locally first if they haven't been built yet:

# Install all modules locally (required before running single-module tests)
./scripts/install_local.sh

# Run the specific tests for the new method and the coverage test
./mvnw test -pl slack-api-client '-Dtest=test_locally.api.methods.AppsTest,test_locally.api.MethodsTest'

Note: install_local.sh may fail on unrelated example modules (e.g., bolt-quarkus-examples). This is fine as long as slack-api-client and its dependencies (slack-api-model) install successfully.

Summary checklist

When adding a new Slack API method, make sure you have:

  • Looked up the method docs at https://docs.slack.dev/reference/methods/{method.name}
  • Added the method constant in slack-api-client/src/main/java/com/slack/api/methods/Methods.java
  • Created the Request class in slack-api-client/src/main/java/com/slack/api/methods/request/{category}/
  • Created the Response class in slack-api-client/src/main/java/com/slack/api/methods/response/{category}/
  • Added two overloads in slack-api-client/src/main/java/com/slack/api/methods/MethodsClient.java
  • Added two overloads in slack-api-client/src/main/java/com/slack/api/methods/AsyncMethodsClient.java
  • Implemented both overloads in slack-api-client/src/main/java/com/slack/api/methods/impl/MethodsClientImpl.java
  • Implemented both overloads in slack-api-client/src/main/java/com/slack/api/methods/impl/AsyncMethodsClientImpl.java
  • Added toForm() in slack-api-client/src/main/java/com/slack/api/methods/RequestFormBuilder.java
  • Added rate limit tier in slack-api-client/src/main/java/com/slack/api/methods/MethodsRateLimits.java
  • Added rate limit entry in metadata/web-api/rate_limit_tiers.json
  • Created mock server JSON fixture in json-logs/samples/api/{method.name}.json
  • Added sync and async tests in slack-api-client/src/test/java/test_locally/api/methods/{Category}Test.java
  • Updated the endpoint list and count in slack-api-client/src/test/java/test_locally/api/MethodsTest.java
  • Added import statements for the new Request/Response classes in all modified files
  • Verified tests pass: ./mvnw test -pl slack-api-client '-Dtest=test_locally.api.methods.{Category}Test,test_locally.api.MethodsTest'

Testing

Test Framework

  • JUnit 4 with @Test, @Before, @After annotations
  • Hamcrest matchers for assertions: assertThat(value, is(expected))
  • MockSlackApiServer for HTTP-level mocking (custom mock server, not Mockito-based HTTP)
  • Test methods use the configurator lambda pattern: slack.methods(ValidToken).methodName(r -> r.field("value"))

Test Organization

src/test/java/
  test_locally/           # Runs in CI - no external dependencies
    api/methods/          # API client tests
    api/model/            # Model serialization tests
  test_with_remote_apis/  # Manual only - requires Slack workspace tokens
  util/                   # Test utilities (mock servers, helpers)
  config/                 # Test configuration

Running Tests

# CI tests only (recommended for development)
./scripts/run_no_prep_tests.sh

# All Bolt framework tests
./scripts/run_all_bolt_tests.sh

# All client tests
./scripts/run_all_client_tests.sh

# Single module tests
./mvnw test -pl slack-api-client '-Dtest=test_locally.**.*Test'

Code Coverage

  • CodeCov with patch target of 30% and project threshold of 1.5%
  • JaCoCo plugin generates coverage reports

Bolt Framework Concepts

For reference when working on bolt or bolt-* modules:

  • App: Central class. Listeners are registered on it via app.command(), app.event(), app.blockAction(), etc.
  • Acknowledgment: All interactions must call ctx.ack() within 3 seconds
  • Context (ctx): Provides ctx.client() (pre-authenticated API client), ctx.say() (post to channel), ctx.respond() (use response_url)
  • Middleware: Chain of handlers. Built-in middleware handles request verification, auth, SSL checks. Custom middleware can be added.
  • Listener types: command, event, blockAction, blockSuggestion, viewSubmission, viewClosed, globalShortcut, messageShortcut, message, function

Documentation: https://docs.slack.dev/tools/java-slack-sdk/guides/bolt-basics

Release Process

Releases publish all modules simultaneously to Maven Central via Sonatype.

  • Snapshot: Version must end with -SNAPSHOT. Run scripts/set_version.sh 1.x.y-SNAPSHOT then scripts/release.sh
  • Stable: Run scripts/set_version.sh 1.x.y then scripts/release.sh. Requires JDK 17, GPG signing, and Sonatype credentials in ~/.m2/settings.xml
  • Version is set across all pom.xml files AND three generated version classes (generated by scripts/set_version.sh):
    • slack-api-model/src/main/java/com/slack/api/meta/SlackApiModelLibraryVersion.java
    • slack-api-client/src/main/java/com/slack/api/meta/SlackApiClientLibraryVersion.java
    • bolt/src/main/java/com/slack/api/bolt/meta/BoltLibraryVersion.java
  • Tags follow v{version} convention. Semantic Versioning is used.
  • set_version.sh requires gnu-sed (gsed) on macOS: brew install gnu-sed

Repository Governance

  • CODEOWNERS: @slackapi/slack-platform-java reviews all changes; @slackapi/developer-education reviews /docs/
  • CLA: Contributors must sign the Salesforce Contributor License Agreement
  • Branch: main is the active development branch
  • PR Template: Categorize changes and reference related issues
  • Dependabot: Auto-merges patch/minor dependency updates monthly

Common Pitfalls

  • Do not use Java 9+ features. The project targets Java 8. This includes List.of(), Map.of(), var, Stream.toList(), HttpClient, text blocks, records, sealed classes, and pattern matching.
  • Do not upgrade SLF4J to v2. It would break users on SLF4J v1 bindings. See #1034.
  • Do not upgrade Mockito to v5+. It requires Java 11+ and would break JDK 8 tests.
  • Gson, not Jackson. This project uses Gson for JSON. Do not introduce Jackson dependencies.
  • JUnit 4, not JUnit 5. Tests use @Test from org.junit.Test, assertions from Hamcrest, not JUnit 5's @Test or Assertions.
  • snake_case JSON, camelCase Java. Gson's field naming policy handles the mapping. Do not use @SerializedName unless the API field name doesn't follow snake_case convention.
  • Two overloads per API method. Every MethodsClient method needs both the direct Request object overload and the RequestConfigurator lambda overload.
  • Async variants. Every sync method in MethodsClient has a corresponding async method in AsyncMethodsClient returning CompletableFuture.
  • Test placement matters. Tests in test_locally run in CI. Tests in test_with_remote_apis require Slack API tokens and only run manually.
  • The set_version.sh script generates source files. The three *LibraryVersion.java files are generated by the version script, not hand-edited. They will show up as changes after running the script.

Development Philosophy

  • Always follow existing architecture patterns. Before implementing anything new, study how similar features are already implemented in the codebase and replicate those patterns. The codebase has strong internal consistency — every API method follows the same structure across Methods.java, Request/Response classes, MethodsClient, AsyncMethodsClient, MethodsClientImpl, AsyncMethodsClientImpl, RequestFormBuilder, MethodsRateLimits, and tests.

  • Reuse existing utilities and abstractions. Look for existing helper methods, base classes, and shared utilities before writing new code. Key reusable patterns include:

    • setIfNotNull() in RequestFormBuilder for mapping request fields to form parameters
    • postFormWithTokenAndParseResponse() in MethodsClientImpl for sending authenticated requests
    • executor.execute() in AsyncMethodsClientImpl for wrapping sync calls in async futures
    • GSON.toJson() in RequestFormBuilder for serializing complex nested objects
    • SlackApiTextResponse as the base interface for all API responses
  • Match naming conventions exactly. Follow the established naming patterns for classes, methods, packages, and constants:

    • Method constants: API method name with dots replaced by underscores, in UPPER_SNAKE_CASE (e.g., conversations.requestSharedInvite.approveCONVERSATIONS_REQUEST_SHARED_INVITE_APPROVE)
    • Java method names: API method name in camelCase with dots removed (e.g., conversationsRequestSharedInviteApprove)
    • Request/Response classes: method name in PascalCase + Request/Response suffix
    • Package paths: API namespace segments in snake_case directories (e.g., request/conversations/request_shared_invite/)
  • Maintain structural consistency. New files should be placed in packages that follow the same hierarchy as existing related files. Request classes go in request/{category}/, response classes go in response/{category}/, and tests go in test_locally/api/methods/{Category}Test.java.

  • When in doubt, find the nearest precedent. If you are unsure how to implement something, find the most similar existing implementation and follow it exactly. For example, when adding a new conversations.* method, look at how existing conversations.* methods are implemented across all the files listed in the checklist above.