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 tool: Apache Maven via Maven Wrapper (
./mvnw) - Java target: Source and target compatibility is Java 1.8 (OpenJDK 8+) — see
pom.xmlpropertiesmaven.compiler.sourceandmaven.compiler.target - Group ID:
com.slack.api— seepom.xml<groupId> - Current version: See
<version>in rootpom.xml(also reflected in*LibraryVersion.javafiles generated byscripts/set_version.sh)
# 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-SNAPSHOTCI 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.
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.
-
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
-
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— The official Slack Bolt framework for Java, used for building modern Slack apps. Maven Central
-
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— 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-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-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-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
bolt-kotlin-examples, bolt-quarkus-examples, bolt-spring-boot-examples, bolt-docker-examples, bolt-google-cloud-functions-example
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.
- Java 8 compatibility is mandatory (see
pom.xmlmaven.compiler.sourceandmaven.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_UNDERSCORESpolicy (seeslack-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 alogfield). - Checked exceptions: API methods throw
IOExceptionandSlackApiException.
- Request classes:
{MethodName}Requestwith@Data @Builderand implementingSlackApiRequest(seeslack-api-client/src/main/java/com/slack/api/methods/SlackApiRequest.java) - Response classes:
{MethodName}Responsewith@Dataand implementingSlackApiTextResponse(seeslack-api-client/src/main/java/com/slack/api/methods/SlackApiTextResponse.java) - API method constants:
Methods.javahaspublic static final Stringfor each API method (e.g.,USERS_INFO = "users.info") — seeslack-api-client/src/main/java/com/slack/api/methods/Methods.java - Test packages:
test_locallyfor CI-safe tests,test_with_remote_apisfor integration tests needing tokens
- Use
GsonFactory.createSnakeCase()for most Slack APIs (snake_case JSON keys withLOWER_CASE_WITH_UNDERSCORESpolicy) - Use
GsonFactory.createCamelCase()for SCIM APIs - The factory implementation is in
slack-api-client/src/main/java/com/slack/api/util/json/GsonFactory.java
The slack-api-model module contains all core data types. When adding or modifying model objects, follow these patterns.
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;
}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;
// ...
}
}- Use
@Data,@Builder,@NoArgsConstructor,@AllArgsConstructor,@EqualsAndHashCode(callSuper = false)on concrete types. - Define
public static final String TYPE(orTYPE_NAME) andprivate final String type = TYPE;. - Java fields are camelCase; Gson's
LOWER_CASE_WITH_UNDERSCORESpolicy (seeGsonFactory.java) maps them to snake_case JSON automatically. - Use
@SerializedNameonly when the Slack API field name does not follow snake_case convention. - Do not initialize
Listfields to empty collections — Slack sometimes returns errors with empty arrays, and null vs empty has semantic meaning.
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.
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.
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";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).
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;
}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;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);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());
}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());
}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;
}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);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",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": ""
}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));
}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.
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 bothrequestandresponseclassesAsyncMethodsClient.java— import bothrequestandresponseclassesMethodsClientImpl.java— import bothrequestandresponseclassesAsyncMethodsClientImpl.java— import bothrequestandresponseclassesRequestFormBuilder.java— import therequestclass only
Place each new import adjacent to the existing imports from the same apps.* (or equivalent) package, in alphabetical order.
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.
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()inslack-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'
- JUnit 4 with
@Test,@Before,@Afterannotations - 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"))
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
# 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'- CodeCov with patch target of 30% and project threshold of 1.5%
- JaCoCo plugin generates coverage reports
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): Providesctx.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
Releases publish all modules simultaneously to Maven Central via Sonatype.
- Snapshot: Version must end with
-SNAPSHOT. Runscripts/set_version.sh 1.x.y-SNAPSHOTthenscripts/release.sh - Stable: Run
scripts/set_version.sh 1.x.ythenscripts/release.sh. Requires JDK 17, GPG signing, and Sonatype credentials in~/.m2/settings.xml - Version is set across all
pom.xmlfiles AND three generated version classes (generated byscripts/set_version.sh):slack-api-model/src/main/java/com/slack/api/meta/SlackApiModelLibraryVersion.javaslack-api-client/src/main/java/com/slack/api/meta/SlackApiClientLibraryVersion.javabolt/src/main/java/com/slack/api/bolt/meta/BoltLibraryVersion.java
- Tags follow
v{version}convention. Semantic Versioning is used. set_version.shrequiresgnu-sed(gsed) on macOS:brew install gnu-sed
- CODEOWNERS:
@slackapi/slack-platform-javareviews all changes;@slackapi/developer-educationreviews/docs/ - CLA: Contributors must sign the Salesforce Contributor License Agreement
- Branch:
mainis the active development branch - PR Template: Categorize changes and reference related issues
- Dependabot: Auto-merges patch/minor dependency updates monthly
- 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
@Testfromorg.junit.Test, assertions from Hamcrest, not JUnit 5's@TestorAssertions. - snake_case JSON, camelCase Java. Gson's field naming policy handles the mapping. Do not use
@SerializedNameunless the API field name doesn't follow snake_case convention. - Two overloads per API method. Every
MethodsClientmethod needs both the directRequestobject overload and theRequestConfiguratorlambda overload. - Async variants. Every sync method in
MethodsClienthas a corresponding async method inAsyncMethodsClientreturningCompletableFuture. - Test placement matters. Tests in
test_locallyrun in CI. Tests intest_with_remote_apisrequire Slack API tokens and only run manually. - The
set_version.shscript generates source files. The three*LibraryVersion.javafiles are generated by the version script, not hand-edited. They will show up as changes after running the script.
-
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()inRequestFormBuilderfor mapping request fields to form parameterspostFormWithTokenAndParseResponse()inMethodsClientImplfor sending authenticated requestsexecutor.execute()inAsyncMethodsClientImplfor wrapping sync calls in async futuresGSON.toJson()inRequestFormBuilderfor serializing complex nested objectsSlackApiTextResponseas 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.approve→CONVERSATIONS_REQUEST_SHARED_INVITE_APPROVE) - Java method names: API method name in
camelCasewith dots removed (e.g.,conversationsRequestSharedInviteApprove) - Request/Response classes: method name in
PascalCase+Request/Responsesuffix - Package paths: API namespace segments in
snake_casedirectories (e.g.,request/conversations/request_shared_invite/)
- Method constants: API method name with dots replaced by underscores, in
-
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 inresponse/{category}/, and tests go intest_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 existingconversations.*methods are implemented across all the files listed in the checklist above.