# Slack Platform Documentation - Java (Bolt for Java + Java Slack SDK) > Documentation for the Java (Bolt for Java + Java Slack SDK) stack only. Pair with llms-full-platform.txt for platform concepts, auth, and API reference. > Generated from the built site. Each page is also available individually at its URL + `.md`. > Page index: https://docs.slack.dev/llms-sitemap.md Source: https://docs.slack.dev/tools/java-slack-sdk # Java Slack SDK The Java Slack SDK supports the Slack platform in a Java idiomatic way. Developers can use it in any JVM language including Kotlin, Groovy, and Scala. Within these docs, there are two different modules: * [Bolt for Java Guides](/tools/java-slack-sdk/guides/getting-started-with-bolt), which is a framework with a simplified API that helps write Slack apps in Java. * [Java SDK Guides](/tools/java-slack-sdk/guides/web-api-basics), for when you need a more customized approach to building a Slack app in Java using the Slack API client. ## Requirements {#requirements} The Java Slack SDK supports **OpenJDK 8 and higher LTS versions**. Users can expect every patch release has been done after verifying functionality by running the basic CI builds with all LTS versions and all the unit tests have passed on at least the latest LTS version. We don't run comprehensive verifications with all OpenJDK distributions, but it should work with all of them. ## Getting help {#getting-help} These docs have lots of information on Bolt for Java and the Java Slack SDK. There's also an in-depth Reference section. Please explore! If you get stuck, we're here to help. The following are the best ways to get assistance working through your issue: * Visit the [Issue Tracker](http://github.com/slackapi/java-slack-sdk/issues) for questions, bug reports, feature requests, and general discussion related to Bolt for Java. Try searching for an existing issue before creating a new one. * Visit the [Slack Developer Community](https://slackcommunity.com/) for getting help or for bonding with your fellow Slack developers. * [Email](mailto:support@slack.com) our developer support team: `support@slack.com`. ## Release notes {#release-notes} Check out the [Java Slack SDK release notes](https://github.com/SlackAPI/java-slack-sdk/releases) for all the latest happenings. ## Contributing {#contributing} These docs live within the [Java Slack SDK](https://github.com/slackapi/java-slack-sdk/) repository and are open source. We welcome contributions from everyone! Please check out our [Contributor's Guide](https://github.com/slackapi/java-slack-sdk/blob/main/.github/contributing.md) for how to contribute in a helpful and collaborative way. --- Source: https://docs.slack.dev/tools/java-slack-sdk/guides/ai-apps # Using AI in apps This feature requires a paid plan If you don't have a paid workspace for development, you can join the [Developer Program](https://api.slack.com/developer-program) and provision a sandbox with access to all Slack features for free. The Agents & AI Apps feature comprises a unique messaging experience for Slack. If you're unfamiliar with using the Agents & AI Apps feature within Slack, you'll want to read the [API documentation on the subject](/ai/). Then come back here to implement them with Bolt! ## Configuring your app to support platform AI features {#configuring-your-app} 1. Within [app settings](https://api.slack.com/apps), enable the **Agents & AI Apps** feature. 2. Within the app settings **OAuth & Permissions** page, add the following scopes: * [`assistant:write`](/reference/scopes/assistant.write) * [`chat:write`](/reference/scopes/chat.write) * [`im:history`](/reference/scopes/im.history) 3. Within the app settings **Event Subscriptions** page, subscribe to the following events: * [`assistant_thread_started`](/reference/events/assistant_thread_started) * [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed) * [`message.im`](/reference/events/message.im) ## The Assistant class instance {#assistant-class} The [`Assistant`](/tools/java-slack-sdk/reference#the-assistantconfig-configuration-object) class can be used to handle the incoming events expected from a user interacting with an app in Slack that has the Agents & AI Apps feature enabled. A typical flow would look like: 1. [The user starts a thread](#handling-a-new-thread). The `Assistant` class handles the incoming [`assistant_thread_started`](/reference/events/assistant_thread_started) event. 2. [The thread context may change at any point](#handling-thread-context-changes). The `Assistant` class can handle any incoming [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed) events. The class also provides a default `context` store to keep track of thread context changes as the user moves through Slack. 3. [The user responds](#handling-user-response). The `Assistant` class handles the incoming [`message.im`](/reference/events/message.im) event. ``` App app = new App();Assistant assistant = new Assistant(app.executorService());assistant.threadStarted((req, ctx) -> { try { ctx.say(r -> r.text("Hi, how can I help you today?")); ctx.setSuggestedPrompts(Collections.singletonList( SuggestedPrompt.create("What does SLACK stand for?") )); } catch (Exception e) { ctx.logger.error("Failed to handle assistant thread started event: {e}", e); }});assistant.userMessage((req, ctx) -> { try { ctx.setStatus("is typing..."); Thread.sleep(500L); if (ctx.getThreadContext() != null && ctx.getThreadContext().getChannelId() != null) { String contextChannel = ctx.getThreadContext().getChannelId(); ctx.say(r -> r.text("I am aware of the channel context: <#" + contextChannel + ">")); } else { ctx.say(r -> r.text("Here you are!")); } } catch (Exception e) { ctx.logger.error("Failed to handle assistant thread started event: {e}", e); try { ctx.say(r -> r.text(":warning: Sorry, something went wrong during processing your request!")); } catch (Exception ee) { ctx.logger.error("Failed to inform the error to the end-user: {ee}", ee); } }});app.assistant(assistant); ``` While the `assistant_thread_started` and `assistant_thread_context_changed` events do provide Slack-client thread context information, the `message.im` event does not. Any subsequent user message events won't contain thread context data. For that reason, Bolt not only provides a way to store thread context — the `threadContextService` property — but it also provides a `DefaultAssistantThreadContextService` instance that is utilized by default. This implementation relies on storing and retrieving [message metadata](/messaging/message-metadata/) as the user interacts with the app. If you do provide your own `threadContextService` property, it must feature `get` and `save` methods. Tip Be sure to give the [reference docs](/tools/java-slack-sdk/reference#agents--assistants) a look! ## Handling a new thread {#handling-a-new-thread} When the user opens a new thread with your AI-enabled app, the [`assistant_thread_started`](/reference/events/assistant_thread_started) event will be sent to your app. Tip When a user opens a thread with your app while in a channel, the channel info is stored as the thread's `AssistantThreadContext` data. You can grab that info by using the `context.getThreadContext()` utility, as subsequent user message event payloads won't include the channel info. ### Block Kit interactions in the app thread {#block-kit-interactions} For advanced use cases, Block Kit buttons may be used instead of suggested prompts, as well as the sending of messages with structured [metadata](/messaging/message-metadata/) to trigger subsequent interactions with the user. For example, an app can display a button like "Summarize the referring channel" in the initial reply. When the user clicks the button and submits detailed information (such as the number of messages, days to check, the purpose of the summary, etc.), the app can handle that information and post a message that describes the request with structured metadata. By default, apps can't respond to their own bot messages (Bolt prevents infinite loops by default). However, if you set `ignoringSelfAssistantMessageEventsEnabled` to false and add a `botMessage` listener to your `Assistant` middleware, your app can continue processing the request as shown below: ``` App app = new App(AppConfig.builder() .singleTeamBotToken(System.getenv("SLACK_BOT_TOKEN")) .ignoringSelfAssistantMessageEventsEnabled(false) .build());Assistant assistant = new Assistant(app.executorService());assistant.threadStarted((req, ctx) -> { try { ctx.say(r -> r .text("Hi, how can I help you today?") .blocks(Arrays.asList( section(s -> s.text(plainText("Hi, how can I help you today?"))), actions(a -> a.elements(Collections.singletonList( button(b -> b.actionId("assistant-generate-numbers").text(plainText("Generate numbers"))) ))) )) ); } catch (Exception e) { ctx.logger.error("Failed to handle assistant thread started event: {e}", e); }});app.blockAction("assistant-generate-numbers", (req, ctx) -> { app.executorService().submit(() -> { Map eventPayload = new HashMap<>(); eventPayload.put("num", 20); try { ctx.client().chatPostMessage(r -> r .channel(req.getPayload().getChannel().getId()) .threadTs(req.getPayload().getMessage().getThreadTs()) .text("OK, I will generate numbers for you!") .metadata(new Message.Metadata("assistant-generate-numbers", eventPayload)) ); } catch (Exception e) { ctx.logger.error("Failed to post a bot message: {e}", e); } }); return ctx.ack();});assistant.botMessage((req, ctx) -> { if (req.getEvent().getMetadata() != null && req.getEvent().getMetadata().getEventType().equals("assistant-generate-numbers")) { try { ctx.setStatus("is typing..."); Double num = (Double) req.getEvent().getMetadata().getEventPayload().get("num"); Set numbers = new HashSet<>(); SecureRandom random = new SecureRandom(); while (numbers.size() < num) { numbers.add(String.valueOf(random.nextInt(100))); } Thread.sleep(1000L); ctx.say(r -> r.text("Her you are: " + String.join(", ", numbers))); } catch (Exception e) { ctx.logger.error("Failed to handle assistant bot message event: {e}", e); } }});assistant.userMessage((req, ctx) -> { try { ctx.setStatus("is typing..."); ctx.say(r -> r.text("Sorry, I couldn't understand your comment.")); } catch (Exception e) { ctx.logger.error("Failed to handle assistant user message event: {e}", e); try { ctx.say(r -> r.text(":warning: Sorry, something went wrong during processing your request!")); } catch (Exception ee) { ctx.logger.error("Failed to inform the error to the end-user: {ee}", ee); } }});app.assistant(assistant); ``` ## Handling thread context changes {#handling-thread-context-changes} When the user switches channels, the [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed) event will be sent to your app. If you use the built-in `Assistant` middleware without any custom configuration, the updated context data is automatically saved as [message metadata](/messaging/message-metadata/) of the first reply from the assistant bot. As long as you use the built-in approach, you don't need to store the context data within a datastore. The downside of this default behavior is the overhead of additional calls to the Slack API. These calls include those to `conversations.history`, which are used to look up the stored message metadata that contains the thread context (via `context.getThreadContextService().findCurrentContext(channelId, threadTs)`). If you prefer storing this data elsewhere, you can pass your own custom `AssistantThreadContextService` implementation to the `Assistant` constructor. We provide `DefaultAssistantThreadContextService`, which is a reference implementation that uses the app thread message metadata. You can use this for production apps, but if you want to use a different datastore for it, you can implement your own class that inherits `AssistantThreadContextService` interface. ``` Assistant assistant = new Assistant(new YourOwnAssistantThreadContextService()); ``` ## Handling the user response {#handling-user-response} When the user messages your app, the [`message.im`](/reference/events/message.im) event will be sent to your app. Messages sent to the app do not contain a [subtype](/reference/events/message) and must be deduced based on their shape and any provided [message metadata](/messaging/message-metadata/). There are three utilities that are particularly useful in curating the user experience: * [`say`](https://oss.sonatype.org/service/local/repositories/releases/archive/com/slack/api/bolt/1.49.0/bolt-1.49.0-javadoc.jar/!/com/slack/api/bolt/context/builtin/EventContext.html#say\(com.slack.api.bolt.util.BuilderConfigurator\)) * [`setTitle`](https://oss.sonatype.org/service/local/repositories/releases/archive/com/slack/api/bolt/1.49.0/bolt-1.49.0-javadoc.jar/!/com/slack/api/bolt/context/builtin/EventContext.html#setTitle\(com.slack.api.RequestConfigurator\)) * [`setStatus`](https://oss.sonatype.org/service/local/repositories/releases/archive/com/slack/api/bolt/1.49.0/bolt-1.49.0-javadoc.jar/!/com/slack/api/bolt/context/builtin/EventContext.html#setStatus\(com.slack.api.RequestConfigurator\)) ## Full example: Assistant Simple App {#full-example} Below is the `AssistantSimpleApp.java` file of the [Assistant Template repo](https://github.com/slackapi/java-slack-sdk/tree/d29a29afff9f2a518495d618502cb7b292e2eb14/bolt-socket-mode/src/test/java/samples) we've created for you to build off of. ``` package samples;import com.slack.api.bolt.App;import com.slack.api.bolt.AppConfig;import com.slack.api.bolt.middleware.builtin.Assistant;import com.slack.api.bolt.socket_mode.SocketModeApp;import com.slack.api.model.assistant.SuggestedPrompt;import com.slack.api.model.event.AppMentionEvent;import com.slack.api.model.event.MessageEvent;import java.util.Arrays;import java.util.Collections;public class AssistantSimpleApp { public static void main(String[] args) throws Exception { String botToken = System.getenv("SLACK_BOT_TOKEN"); String appToken = System.getenv("SLACK_APP_TOKEN"); App app = new App(AppConfig.builder().singleTeamBotToken(botToken).build()); Assistant assistant = new Assistant(app.executorService()); assistant.threadStarted((req, ctx) -> { try { ctx.say("Hi, how can I help you today?"); ctx.setSuggestedPrompts(r -> r .title("Select one of the following:") // optional .prompts(Collections.singletonList(SuggestedPrompt.create("What does SLACK stand for?"))) ); } catch (Exception e) { ctx.logger.error("Failed to handle assistant thread started event: {e}", e); } }); assistant.userMessage((req, ctx) -> { try { // ctx.setStatus(r -> r.status("is typing...")); works too ctx.setStatus("is typing..."); Thread.sleep(500L); if (ctx.getThreadContext() != null && ctx.getThreadContext().getChannelId() != null) { String contextChannel = ctx.getThreadContext().getChannelId(); ctx.say("I am aware of the channel context: <#" + contextChannel + ">"); } else { ctx.say("Here you are!"); } } catch (Exception e) { ctx.logger.error("Failed to handle assistant user message event: {e}", e); try { ctx.say(":warning: Sorry, something went wrong during processing your request!"); } catch (Exception ee) { ctx.logger.error("Failed to inform the error to the end-user: {ee}", ee); } } }); assistant.userMessageWithFiles((req, ctx) -> { try { ctx.setStatus("is downloading the files..."); Thread.sleep(500L); ctx.setStatus("is analyzing the files...", Arrays.asList("Reading bytes...", "Confirming hashes...")); Thread.sleep(500L); ctx.say("Your files do not have any issues!"); } catch (Exception e) { ctx.logger.error("Failed to handle assistant user message event: {e}", e); try { ctx.say(":warning: Sorry, something went wrong during processing your request!"); } catch (Exception ee) { ctx.logger.error("Failed to inform the error to the end-user: {ee}", ee); } } }); app.use(assistant); app.event(MessageEvent.class, (req, ctx) -> { return ctx.ack(); }); app.event(AppMentionEvent.class, (req, ctx) -> { ctx.say("I can help you at our 1:1 DM!"); return ctx.ack(); }); new SocketModeApp(appToken, app).start(); }} ``` --- Source: https://docs.slack.dev/tools/java-slack-sdk/guides/app-distribution # App Distribution (OAuth) A newly created Slack app can only be installed in its development workspace in the beginning. By setting an OAuth Redirect URL and enabling [App Distribution](/app-management/distribution), the app becomes to be ready for installation in other workspaces. * [Installing with OAuth](/authentication/installing-with-oauth) * [Distributing Slack apps](/app-management/distribution) ### Slack app configuration {#slack-app-configuration} To enable App Distribution, visit the [Slack app settings page](http://api.slack.com/apps), choose the app you're working on, go to **Settings** > **Manage Distribution** on the left pane, and follow the instructions there. For **Redirect URL**, Bolt apps respond to `https://{your app's public URL domain}/slack/oauth/callback` if you go with recommended settings. To know how to configure such settings, consult the list of the available env variables below in this page. Bolt for Java automatically includes support for [org-wide installations](/enterprise) since version `1.4.0`. Org-wide installations can be enabled in your app settings under **Org Level Apps**. ### What your Bolt app does {#what-your-bolt-app-does} To properly handle the OAuth flow: * Provide an endpoint starting the OAuth flow by redirecting installers to the `authorize` endpoint with sufficient parameters * Generate a `state` parameter value to verify afterwards * Append `client_id`, `scope`, `user_scope` (only for v2), and `state` to the URL * Provide an endpoint to handle user redirection from Slack * Make sure if the `state` parameter is valid * Complete the installation by calling the [`oauth.v2.access`](/reference/methods/oauth.v2.access) method (or the [`oauth.access`](/reference/methods/oauth.access) method if you maintain legacy OAuth apps) method and store the acquired tokens * Provide the endpoints to navigate installers for the completion/cancellation of the installation flow * The URLs are usually somewhere else but Bolt has simple functionality to serve them * * * ## Examples {#examples} Here is a Bolt app demonstrating how to implement an OAuth flow. As the OAuth flow handling features are unnecessary for many custom apps, those are disabled by default. `App` instances need to explicitly call the `asOAuthApp(true)` method to turn them on. ``` import com.slack.api.bolt.App;import com.slack.api.bolt.jetty.SlackAppServer;import java.util.HashMap;import java.util.Map;import static java.util.Map.entry;// API Request Handler App// expected env variables:// SLACK_SIGNING_SECRETApp apiApp = new App();apiApp.command("/hi", (req, ctx) -> { return ctx.ack("Hi there!");});// OAuth Flow Handler App// expected env variables:// SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_REDIRECT_URI, SLACK_SCOPES,// SLACK_INSTALL_PATH, SLACK_REDIRECT_URI_PATH// SLACK_OAUTH_COMPLETION_URL, SLACK_OAUTH_CANCELLATION_URLApp oauthApp = new App().asOAuthApp(true);// Mount the two apps with their root pathSlackAppServer server = new SlackAppServer(new HashMap<>(Map.ofEntries( entry("/slack/events", apiApp), // POST /slack/events (incoming API requests from the Slack Platform) entry("/slack/oauth", oauthApp) // GET /slack/oauth/start, /slack/oauth/callback (user access))));server.start(); // http://localhost:3000 ``` Technically, it's possible to use a single `App` for both Slack API requests and direct user interactions for the OAuth flow, but most apps will prefer to have a different root path for OAuth interactions. ### Slack config for distributing your Slack app {#slack-config-for-distributing-your-slack-app} Here is the list of the necessary configurations for distributing apps built with Bolt. If you prefer using other env variable names or other solutions to load this information, implement your own way to load **AppConfig** instead. Env Variable Name Description (Where to find the value) `SLACK_SIGNING_SECRET` A secret key for verifying requests from Slack. (Find at **Settings** > **Basic Information** > **App Credentials**) `SLACK_CLIENT_ID` OAuth 2.0 Client ID (Find at **Settings** > **Basic Information** > **App Credentials**) `SLACK_CLIENT_SECRET` OAuth 2.0 Client Secret (Find at **Settings** > **Basic Information** > **App Credentials**) `SLACK_REDIRECT_URI` OAuth 2.0 Redirect URI (Configure at **Features** > **OAuth & Permissions** > **Redirect URLs**) `SLACK_SCOPES` Comma-separated list of scopes: `scope` parameter that will be appended to `https://slack.com/oauth/authorize` and `https://slack.com/oauth/v2/authorize` as a query parameter (Find at **Settings** > **Manage Distribution** > **Sharable URL**, extract the value for `scope`) `SLACK_USER_SCOPES` (only for v2) Comma-separated list of user scopes: `user_scope` parameter that will be appended to `https://slack.com/oauth/v2/authorize` as a query parameter (Find at **Settings** > **Manage Distribution** > **Sharable URL**, extract the value for `user_scope`) `SLACK_INSTALL_PATH` Starting point of OAuth flow; this endpoint redirects users to the Slack `authorize` endpoint with required query parameters such as `client_id`, `scope`, `user_scope` (only for v2), and `state`. The suggested path is `/slack/oauth/start`, but you can use any path. Note that the example above automatically prepends `/slack/oauth` to this variable. `SLACK_REDIRECT_URI_PATH` Path for OAuth Redirect URI; this endpoint handles callback requests after the Slack OAuth confirmation. The path must be consistent with the `SLACK_REDIRECT_URI` value. The suggested path is `/slack/oauth/callback`, but you can use any path. Note that the example above automatically prepends `/slack/oauth` to this variable. `SLACK_OAUTH_COMPLETION_URL` Installation Completion URL; the complete public URL to redirect users to when their installations have been successfully completed. You can use any URL. `SLACK_OAUTH_CANCELLATION_URL` Installation Cancellation/Error URL; the complete public URL to redirect users to when their installations have been cancelled. You can use any URL. ### Choose Proper Storage Services {#choose-proper-storage-services} By default, OAuth flow-supported Bolt apps uses the local file system to generate and store state parameters, and store bot and user tokens. Bolt supports the following out-of-the-box. * Local File System * Amazon S3 * Relational database (via JDBC) - [_coming soon!_](https://github.com/slackapi/java-slack-sdk/issues/347) If your datastore is unsupported, you can implement the interfaces `com.slack.api.bolt.service.InstallationService` and `com.slack.api.bolt.service.OAuthStateService` on your own. Here is an example app demonstrating how to enable Amazon S3 backed services. ``` import com.slack.api.bolt.App;import com.slack.api.bolt.jetty.SlackAppServer;import com.slack.api.bolt.service.InstallationService;import com.slack.api.bolt.service.OAuthStateService;import com.slack.api.bolt.service.builtin.AmazonS3InstallationService;import com.slack.api.bolt.service.builtin.AmazonS3OAuthStateService;import java.util.HashMap;import java.util.Map;import static java.util.Map.entry;// The standard AWS env variables are expected// export AWS_REGION=us-east-1// export AWS_ACCESS_KEY_ID=AAAA*************// export AWS_SECRET_ACCESS_KEY=4o7***********************// Please be careful about the security policies on this bucket.String awsS3BucketName = "YOUR_OWN_BUCKET_NAME_HERE";InstallationService installationService = new AmazonS3InstallationService(awsS3BucketName);// Set true if you'd like to store every single installation as a different recordinstallationService.setHistoricalDataEnabled(true);// apiApp uses only InstallationService to access stored tokensApp apiApp = new App();apiApp.command("/hi", (req, ctx) -> { return ctx.ack("Hi there!");});apiApp.service(installationService);// Needless to say, oauthApp uses InstallationService// In addition, it uses OAuthStateService to create/read/delete state parametersApp oauthApp = new App().asOAuthApp(true);oauthApp.service(installationService);// Store valid state parameter values in Amazon S3 storageOAuthStateService stateService = new AmazonS3OAuthStateService(awsS3BucketName);// This service is necessary only for OAuth flow appsoauthApp.service(stateService);// Mount the two apps with their root pathSlackAppServer server = new SlackAppServer(new HashMap<>(Map.ofEntries( entry("/slack/events", apiApp), // POST /slack/events (incoming API requests from the Slack Platform) entry("/slack/oauth", oauthApp) // GET /slack/oauth/start, /slack/oauth/callback (user access))));server.start(); // http://localhost:3000 ``` If you want to turn the [token rotation feature](/authentication/using-token-rotation) on, your `InstallationService` should be compatible with it. Refer to the [v1.9.0 release notes](https://github.com/slackapi/java-slack-sdk/releases/tag/v1.9.0) for more details. ### Granular permission apps or classic apps {#granular-permission-apps-or-classic-apps} Slack has two types of OAuth flows for Slack app installations. The V2 (this is a bit confusing because it's not the version of OAuth spec, rather the version of the Slack OAuth flow) OAuth flow enables Slack apps to request more granular permissions than the classic ones, especially for bot users. The differences between the two types are having `v2` in the endpoint to issue access tokens and the OAuth authorization URL, plus some changes to the response data structure returned by the `oauth(.v2).access` endpoint. #### V2 OAuth 2.0 Flow (default) {#v2-oauth-20-flow-default} Variable Description Authorization URL `https://slack.com/oauth/v2/authorize` Web API to issue access tokens [`oauth.v2.access`](/reference/methods/oauth.v2.access) ([Response](https://github.com/slackapi/java-slack-sdk/blob/main/slack-api-client/src/main/java/com/slack/api/methods/response/oauth/OAuthV2AccessResponse.java)) #### Classic OAuth Flow {#classic-oauth-flow} Variable Description Authorization URL `https://slack.com/oauth/authorize` Web API to issue access tokens [`oauth.access`](/reference/methods/oauth.access) ([Response](https://github.com/slackapi/java-slack-sdk/blob/main/slack-api-client/src/main/java/com/slack/api/methods/response/oauth/OAuthAccessResponse.java)) By default, Bolt enables the V2 OAuth Flow. It's configurable by the `setClassicAppPermissionsEnabled` method. The value is set to `false` by default. Change the flag to `true` to authorize your classic OAuth apps. ``` AppConfig appConfig = new AppConfig();appConfig.setClassicAppPermissionsEnabled(true);App app = new App(appConfig); ``` `InstallationService` absorbs the difference in the response structure, so you don't need to change anything when you switch from the classic OAuth to the V2. #### Build Slack OAuth using Spring Boot {#build-slack-oauth-using-spring-boot} To implement a Slack OAuth flow: 1. Load `env` variables 2. Initialize `App` with services and listeners, such as a Spring Bean 3. Have three endpoints to handle HTTP requests Tip Bolt properly works with Spring Boot 2.2 or newer versions. ``` package hello;// export SLACK_SIGNING_SECRET=xxx// export SLACK_CLIENT_ID=111.222// export SLACK_CLIENT_SECRET=xxx// export SLACK_SCOPES=commands,chat:write.public,chat:write// export SLACK_USER_SCOPES=// export SLACK_INSTALL_PATH=/slack/install// export SLACK_REDIRECT_URI_PATH=/slack/oauth_redirect// export SLACK_OAUTH_COMPLETION_URL=https://www.example.com/completion// export SLACK_OAUTH_CANCELLATION_URL=https://www.example.com/cancellationimport com.slack.api.bolt.App;import javax.servlet.annotation.WebServlet;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class SlackApp { @Bean public App initSlackApp() { App app = new App().asOAuthApp(true); // Do not forget calling `asOAuthApp(true)` here app.command("/hello-oauth-app", (req, ctx) -> { return ctx.ack("What's up?"); }); return app; }}import com.slack.api.bolt.servlet.SlackAppServlet;import com.slack.api.bolt.servlet.SlackOAuthAppServlet;@WebServlet("/slack/events")public class SlackEventsController extends SlackAppServlet { public SlackEventsController(App app) { super(app); }}@WebServlet("/slack/install")public class SlackOAuthInstallController extends SlackOAuthAppServlet { public SlackOAuthInstallController(App app) { super(app); }}@WebServlet("/slack/oauth_redirect")public class SlackOAuthRedirectController extends SlackOAuthAppServlet { public SlackOAuthRedirectController(App app) { super(app); }} ``` If you want to use different implementations of `InstallationService` and `OAuthStateService`, you can have them as Spring components this way: ``` public class SlackApp { // Please be careful about the security policies on this bucket. private static final String S3_BUCKET_NAME = "your-s3-bucket-name"; @Bean public InstallationService initInstallationService() { InstallationService installationService = new AmazonS3InstallationService(S3_BUCKET_NAME); installationService.setHistoricalDataEnabled(true); return installationService; } @Bean public OAuthStateService initStateService() { return new AmazonS3OAuthStateService(S3_BUCKET_NAME); } @Bean public App initSlackApp(InstallationService installationService, OAuthStateService stateService) { App app = new App().asOAuthApp(true); app.service(installationService); app.service(stateService); return app; }} ``` #### Use the Built-in tokens_revoked / app_uninstalled event handlers {#use-the-built-in-tokens_revoked--app_uninstalled-event-handlers} For secure data management for your customers and end users, properly handling [`tokens_revoked`](/reference/events/tokens_revoked) and [`app_uninstalled`](/reference/events/app_uninstalled) events is crucial. Bolt for Java provides the built-in event handlers for these events, which seamlessly integrate with your `InstallationService`'s deletion methods. ``` App app = new App();InstallationService installationService = new MyInstallationService();app.service(installationService);// Turn the event handlers onapp.enableTokenRevocationHandlers(); ``` The above code is equivalent to the following: ``` App app = new App();InstallationService installationService = new MyInstallationService();app.service(installationService);// Turn the event handlers onapp.event(TokensRevokedEvent.class, app.defaultTokensRevokedEventHandler());app.event(AppUninstalledEvent.class, app.defaultAppUninstalledEventHandler()); ``` To enable your own custom `InstallationService` classes to work with the built-in event handlers, the classes need to implement the following methods in the [`InstallationService`](https://github.com/seratch/java-slack-sdk/blob/main/bolt/src/main/java/com/slack/api/bolt/service/InstallationService.java) interface: * `void deleteBot(Bot bot)` * `void deleteInstaller(Installer installer)` * `void deleteAll(String enterpriseId, String teamId)` #### Serve completion/cancellation pages in Bolt apps {#serve-completioncancellation-pages-in-bolt-apps} Although most apps tend to choose static pages for the completion/cancellation URLs, it's also possible to dynamically serve those URLs in the same app. Bolt does not offer any features to render web pages. Use your favorite template engine for it. ``` String renderCompletionPageHtml(String queryString) { return null; }String renderCancellationPageHtml(String queryString) { return null; }oauthApp.endpoint("GET", "/slack/oauth/completion", (req, ctx) -> { return Response.builder() .statusCode(200) .contentType("text/html") .body(renderCompletionPageHtml(req.getQueryString())) .build();});oauthApp.endpoint("GET", "/slack/oauth/cancellation", (req, ctx) -> { return Response.builder() .statusCode(200) .contentType("text/html") .body(renderCancellationPageHtml(req.getQueryString())) .build();}); ``` --- Source: https://docs.slack.dev/tools/java-slack-sdk/guides/app-home # App Home An [App Home](/surfaces/app-home) is a private, one-to-one space in Slack shared by a user and an app. Each App Home contains a number of tabbed surfaces, including a Messages tab for app-user conversation, and a Home tab that can be fully customized by the app. ### Slack app configuration {#slack-app-configuration} To enable Home tabs, visit the [Slack app settings page](http://api.slack.com/apps), choose the app you're working on, go to **Features** > **App Home** on the left pane, and then turn on **Home Tab**. To enable the Events API, go to **Features** > **Event Subscriptions** on the left pane. There are a few things to do on the page. * Turn on **Enable Events** * Set the **Request URL** to `https://{your app's public URL domain}/slack/events` (this step is not required for Socket Mode apps) * Add subscriptions to bot events * Click **Subscribe to bot events** * Click **Add Bot User Event** button * Choose **app\_home\_opened** event * Click the **Save Changes** button at the bottom for sure ### What your Bolt app does {#what-your-bolt-app-does} To provide Home tabs to your app users: 1. Call the [`views.publish`](/reference/methods/views.publish) method to update the Home tab on a per-user basis 2. Handle any user interactions in Home tab (`block_actions`, `block_suggestion`) Most commonly, [`app_home_opened`](/reference/events/app_home_opened) events would be used as the trigger to call the [`views.publish`](/reference/methods/views.publish) method. Subscribing this event type is useful particularly for the initial Home tab creation. But it's also fine to publish Home tabs by any other means. * * * ## Examples {#examples} Tip If you're a beginner to using Bolt for Slack App development, consult [Getting Started with Bolt](/tools/java-slack-sdk/guides/getting-started-with-bolt), first. The following code calls the [`views.publish`](/reference/methods/views.publish) method when receiving an [`app_home_opened`](/reference/events/app_home_opened) event for the user that triggered the event. The user will see the updated Home tab immediately after the [`views.publish`](/reference/methods/views.publish) call has been successfully completed. ``` import com.slack.api.methods.response.views.ViewsPublishResponse;import com.slack.api.model.event.AppHomeOpenedEvent;import com.slack.api.model.view.View;import java.time.ZonedDateTime;import static com.slack.api.model.block.Blocks.*;import static com.slack.api.model.block.composition.BlockCompositions.*;import static com.slack.api.model.view.Views.*;// /reference/events/app_home_openedapp.event(AppHomeOpenedEvent.class, (payload, ctx) -> { // Build a Home tab view ZonedDateTime now = ZonedDateTime.now(); View appHomeView = view(view -> view .type("home") .blocks(asBlocks( section(section -> section.text(markdownText(mt -> mt.text(":wave: Hello, App Home! (Last updated: " + now + ")")))), image(img -> img.imageUrl("https://www.example.com/foo.png").altText("alt text for image")) )) ); // Update the App Home for the given user if (payload.getEvent().getView() == null) { ViewsPublishResponse res = ctx.client().viewsPublish(r -> r .userId(payload.getEvent().getUser()) .view(appHomeView) ); } else { ViewsPublishResponse res = ctx.client().viewsPublish(r -> r .userId(payload.getEvent().getUser()) .hash(payload.getEvent().getView().getHash()) // To safeguard against potential race conditions .view(appHomeView) ); } return ctx.ack();}); ``` It looks like this in Kotlin. (New to Kotlin? [Getting Started in Kotlin](/tools/java-slack-sdk/guides/getting-started-with-bolt#getting-started-in-kotlin) may be helpful.) ``` // static importsimport com.slack.api.model.block.Blocks.*import com.slack.api.model.block.composition.BlockCompositions.*import com.slack.api.model.view.Views.*import com.slack.api.model.event.AppHomeOpenedEventimport java.time.ZonedDateTime// /reference/events/app_home_openedapp.event(AppHomeOpenedEvent::class.java) { event, ctx -> // Build a Home tab view val now = ZonedDateTime.now() val appHomeView = view { it.type("home") .blocks(asBlocks( section { section -> section.text(markdownText { mt -> mt.text(":wave: Hello, App Home! (Last updated: ${now})") }) }, image { img -> img.imageUrl("https://www.example.com/foo.png").altText("alt text for image") } )) } // Update the App Home for the given user val res = ctx.client().viewsPublish { it.userId(event.event.user) .hash(event.event.view?.hash) // To protect against possible race conditions .view(appHomeView) } ctx.ack()} ``` You can also build the view in the above example with the [Block Kit Kotlin DSL](/tools/java-slack-sdk/guides/composing-messages#block-kit-kotlin-dsl) like so: ``` // These imports are necessary for this codeimport com.slack.api.model.kotlin_extension.view.blocksimport com.slack.api.model.view.Views.viewval appHomeView = view { it .type("home") .blocks { section { markdownText(":wave: Hello, App Home! (Last updated: ${now}") } image { imageUrl("https://www.example.com/foo.png") } }} ``` ## Under the hood {#under-the-hood} Refer to [the Events API guide](/tools/java-slack-sdk/guides/events-api). --- Source: https://docs.slack.dev/tools/java-slack-sdk/guides/audit-logs-api # Audit Logs API The [Audit Logs API](https://api.lack.com/docs/audit-logs-api) is used for monitoring what is happening in your [Enterprise Grid](/enterprise) organization. The Audit Logs API can be used by security information and event management (SIEM) tools to provide an analysis of how your Slack organization is being accessed. You can also use this API to write your own applications to see how members of your organization are using Slack. Follow the instructions in [the API documentation](/admins/audit-logs-api) to get a valid token for using the Audit Logs API. Your Slack app used for accessing the Audit Logs API needs to be installed at the org level on the Enterprise Grid organization, not an individual workspace within the organization. * * * ## Call the Audit Logs API in Java {#call-the-audit-logs-api-in-java} Use the `slack-api-client` library to call the Audit Logs API. ``` import com.slack.api.Slack;import com.slack.api.audit.*;Slack slack = Slack.getInstance();String token = System.getenv("SLACK_ADMIN_ACCESS_TOKN"); // `auditlogs:read` scope requiredAuditClient audit = slack.audit(token); ``` All of the endpoints are supported. Java Method Endpoint `AuditClient#getLogs()` `GET /logs` `AuditClient#getSchemas()` `GET /schemas` `AuditClient#getActions()` `GET /actions` ### getLogs() {#getlogs} This is the primary endpoint for retrieving actual audit events from your organization. It will return a list of actions that have occurred on the installed workspace or grid organization. Authentication is required. ``` import com.slack.api.audit.response.LogsResponse;LogsResponse response = audit.getLogs(req -> req .oldest(1521214343) // Unix timestamp of the least recent audit event to include (inclusive) .action(Actions.User.user_login) // A team member logged in .limit(10) // Number of results to optimistically return); ``` Refer to the [Javadoc](https://oss.sonatype.org/service/local/repositories/releases/archive/com/slack/api/slack-api-client/1.49.0/slack-api-client-1.49.0-javadoc.jar/!/com/slack/api/audit/response/LogsResponse.html) to know the response data structure. ### getSchemas() {#getschemas} This endpoint returns information about the kind of objects, which the Audit Logs API returns as a list of all objects and a short description. Authentication is not required. ``` import com.slack.api.audit.response.SchemasResponse;SchemasResponse response = audit.getSchemas(); ``` Refer to the [Javadoc](https://oss.sonatype.org/service/local/repositories/releases/archive/com/slack/api/slack-api-client/1.49.0/slack-api-client-1.49.0-javadoc.jar/!/com/slack/api/audit/response/SchemasResponse.html) to know the response data structure. ### getActions() {#getactions} This endpoint returns information about the kind of actions that the Audit Logs API returns as [a list of all actions](https://oss.sonatype.org/service/local/repositories/releases/archive/com/slack/api/slack-api-client/1.49.0/slack-api-client-1.49.0-javadoc.jar/!/com/slack/api/audit/Actions.html) and a short description of each. Authentication is not required. ``` import com.slack.api.audit.response.ActionsResponse;ActionsResponse response = audit.getActions(); ``` Refer to the [Javadoc](https://oss.sonatype.org/service/local/repositories/releases/archive/com/slack/api/slack-api-client/1.49.0/slack-api-client-1.49.0-javadoc.jar/!/com/slack/api/audit/response/ActionsResponse.html) to know the response data structure. * * * ## Rate limits {#rate-limits} The Audit Logs API methods conform to Slack's [rate limits](/apis/web-api/rate-limits) and all methods are rated Tier 3. This allows for up to 50 calls per minute, with an allowance for sporadic bursts. Refer to [the API documentation](/admins/audit-logs-api) for more details. The async client, `AsyncAuditClient`, takes rate limits into consideration. The async client internally has its queue systems to avoid burst traffics as much as possible while `AuditClient`, the synchronous client, always blindly sends requests. Both sync and async clients maintain the metrics data in a `MetricsDatastore` together. This allows the async client to accurately know the current traffic they generated toward the Slack platform and estimate the remaining amount to call. The default implementation of the datastore is an in-memory one using the JVM heap memory. The default `SlackConfig` enables the in-memory one. The `AsyncAuditClient` considers the metrics data well. It may delay API requests to avoid rate-limited errors if the clients in the app already sent too many requests within a short period. ``` import com.slack.api.audit.*;import com.slack.api.audit.response.LogsResponse;import java.util.concurrent.CompletableFuture;String token = System.getenv("SLACK_ADMIN_ACCESS_TOKN"); // `auditlogs:read` scope requiredAsyncAuditClient audit = Slack.getInstance().auditAsync(token);CompletableFuture response = audit.getLogs(req -> req .oldest(1521214343) // Unix timestamp of the least recent audit event to include (inclusive) .action(Actions.User.user_login) // A team member logged in .limit(10) // Number of results to optimistically return); ``` --- Source: https://docs.slack.dev/tools/java-slack-sdk/guides/bolt-basics # Bolt Basics **Bolt for Java** is a framework on the JVM that offers an abstraction layer to build Slack apps quickly using modern platform features. If you're not yet familiar with Slack app development in general, we recommend starting with the [API docs](/). * * * ## Start with the App class {#start-with-the-app-class} The `App` class is a place to write only essential parts of your Slack app without being bothered by trifles. The code configuring an `App` instance mainly consists of the ways to respond to incoming events from Slack such as user actions, command invocations, requests to load options in select menus, and any events you subscribe to in the [Events API](/apis/events-api). ``` import com.slack.api.bolt.App;App app = new App();app.command("/echo", (req, ctx) -> { return ctx.ack(req.getText());}); ``` * * * ## Dispatching events {#dispatching-events} Here is the list of the available methods to dispatch events. Method Constraints (value: type) Description `app.attachmentAction` callback\_id: `String` | `Pattern` Legacy Messaging: Responds to user actions in attachments. These events can be triggered in only messages. `app.blockAction` action\_id: `String` | `Pattern` [Interactive Components](/tools/java-slack-sdk/guides/interactive-components): Responds to user actions (e.g., click a button, choose an item from select menus, radio buttons, etc.) in `blocks`. These events can be triggered in all the surfaces (messages, modals, and Home tabs). `app.blockSuggestion` action\_id: `String` | `Pattern` [Interactive Components](/tools/java-slack-sdk/guides/interactive-components): Responds to user actions to input a keyword (the length needs to be the `min_query_length` or longer) in select menus (external data source). `app.command` command name: `String` | `Pattern` [Slash Commands](/tools/java-slack-sdk/guides/slash-commands): Responds to slash command invocations in the workspace. `app.dialogCancellation` callback\_id `String` | `Pattern` Dialogs: Responds to the events where users close dialogs by clicking Cancel buttons. `app.dialogSubmission` callback\_id: `String` | `Pattern` Dialogs: Responds to data submissions in dialogs. `app.dialogSuggestion` callback\_id: `String` | `Pattern` Dialogs: Responds to requests to load options for `"external"` typed select menus in dialogs. `app.event` event type: `Class\` [Events API](/tools/java-slack-sdk/guides/events-api): Responds to any of bot/user events you subscribe to. `app.function` callback\_id: `String` | `Pattern` [Custom steps](/tools/java-slack-sdk/guides/custom-steps): Defines a function that can be used as a custom step in [Workflow Builder](https://slack.com/help/articles/360035692513-Guide-to-Slack-Workflow-Builder). `app.globalShortcut` callback\_id: `String` | `Pattern` [Shortcuts](/tools/java-slack-sdk/guides/shortcuts): Responds to global shortcut invocations. `app.message` keyword: `String` | `Pattern` [Events API](/tools/java-slack-sdk/guides/events-api): Responds to messages posted by a user only when the text in messages matches the given keyword or regular expressions. `app.messageShortcut` callback\_id: `String` | `Pattern` [Shortcuts](/tools/java-slack-sdk/guides/shortcuts): Responds to shortcut invocations in message menus. `app.viewClosed` callback\_id: `String` | `Pattern` [Modals](/tools/java-slack-sdk/guides/modals): Responds to the events where users close modals by clicking Cancel buttons. The `notify_on_close` has to be `true` when opening/pushing the modal. `app.viewSubmission` callback\_id: `String` | `Pattern` [Modals](/tools/java-slack-sdk/guides/modals): Responds to data submissions in modals. * * * ## Development guides by feature {#development-guides-by-feature} On these guide pages, you'll find example code for each. * [Slash Commands](/tools/java-slack-sdk/guides/slash-commands) * [Interactive Components](/tools/java-slack-sdk/guides/interactive-components) * [Modals](/tools/java-slack-sdk/guides/modals) * [Shortcuts](/tools/java-slack-sdk/guides/shortcuts) * [App Home](/tools/java-slack-sdk/guides/app-home) * [Events API](/tools/java-slack-sdk/guides/events-api) * [App Distribution (OAuth Flow)](/tools/java-slack-sdk/guides/app-distribution) * * * ## Acknowledge incoming requests {#acknowledge-incoming-requests} Actions, commands, and options events must always be acknowledged using the `ack()` method. All such utility methods are available as the instance methods of a `Context` object. ``` app.command("/hello", (req, ctx) -> { // ctx: Context return ctx.ack(); // empty body, that means your bot won't post a reply this time}); ``` If your app replies to a user action, you can pass a message text to the `ack()` method. ``` app.command("/ping", (req, ctx) -> { return ctx.ack(":wave: pong");}); ``` It's also possible to use [Block Kit](/block-kit/) to make messages more interactive. ``` import static com.slack.api.model.block.Blocks.*;import static com.slack.api.model.block.composition.BlockCompositions.*;import static com.slack.api.model.block.element.BlockElements.*;app.command("/ping", (req, ctx) -> { return ctx.ack(asBlocks( section(section -> section.text(markdownText(":wave: pong"))), actions(actions -> actions .elements(asElements( button(b -> b.actionId("ping-again").text(plainText(pt -> pt.text("Ping"))).value("ping")) )) ) ));}); ``` By default, the reply will be sent as an ephemeral message. To send a message visible to everyone, use `"in_channel"` type. ``` app.command("/ping", (req, ctx) -> { return ctx.ack(res -> res.responseType("in_channel").text(":wave: pong"));}); ``` Since your app always has to return `ctx.ack()` result within 3 seconds, you may want to asynchronously execute time-consuming parts in your listener. The easiest way to do this would be to use `app.executorService()`, which is the singleton `ExecutorService` instance that Bolt framework provides. ``` app.globalShortcut("callback-id", (req, ctx) -> { // Using the default singleton thread pool app.executorService().submit(() -> { // Do anything asynchronously here try { ctx.client().viewsOpen(r -> r .triggerId(ctx.getTriggerId()) .view(View.builder().build()) ); } catch (Exception e) { // Error handling } }); // This line will be synchronously executed return ctx.ack();}); ``` If you want to take full control of the `ExecutorService` to use, you don't need to use `app.executorService()`. You can go with the preferable way to manage asynchronous code execution for your app. * * * ## Respond to user actions {#respond-to-user-actions} Are you already familiar with `response_url`? If not, we recommend reading [this guide](/interactivity/handling-user-interaction) first. As the guide says, some of the user interaction payloads may contain a `response_url`. This `response_url` is unique to each payload, and can be used to publish messages back to the place where the interaction happened. Similarly to `ack()`above, the `Context` object offers `respond()` method for easily taking advantage of `response_url`. ``` import com.slack.api.webhook.WebhookResponse;app.command("/hello", (req, ctx) -> { // Post a message via response_url WebhookResponse result = ctx.respond(res -> res .responseType("ephemeral") // or "in_channel" .text("Hi there!") // blocks, attachments are also available ); return ctx.ack(); // ack() here doesn't post a message}); ``` * * * ## Use Web APIs / reply using say utility {#use-web-apis--reply-using-say-utility} Use `ctx.client()` to call Slack Web API methods in Bolt apps. The `MethodsClient` created by the method already holds a valid bot token, so there is no need to provide a token to it. Call the method with parameters as below. ``` app.command("/hello", (req, ctx) -> { // ctx.client() holds a valid bot token ChatPostMessageResponse response = ctx.client().chatPostMessage(r -> r .channel(ctx.getChannelId()) .text(":wave: How are you?") ); return ctx.ack();}); ``` For [chat.postMessage](/reference/methods/chat.postMessage) API calls with the given channel ID, using the `say()` utility is simpler. However, if your slash command needs to be available anywhere, using `ctx.respond` would be more robust, as the `say()` method does not work for conversations where the app's bot user is not a member (e.g., a person's own DM). ``` app.command("/hello", (req, ctx) -> { ChatPostMessageResponse response = ctx.say(":wave: How are you?"); return ctx.ack();}); ``` In the case to use a user token over a bot token, overwriting the token by giving a user token as an argument works. ``` import com.slack.api.methods.response.search.SearchMessagesResponse;app.command("/my-search", (req, ctx) -> { String query = req.getPayload().getText(); if (query == null || query.trim().length() == 0) { return ctx.ack("Please give some query."); } String userToken = ctx.getRequestUserToken(); // enabling InstallationService required if (userToken != null) { SearchMessagesResponse response = ctx.client().searchMessages(r -> r .token(userToken) // Overwrite underlying bot token with the given user token .query(query)); if (response.isOk()) { String reply = response.getMessages().getTotal() + " results found for " + query; return ctx.ack(reply); } else { String reply = "Failed to search by " + query + " (error: " + response.getError() + ")"; return ctx.ack(reply); } } else { return ctx.ack("Please allow this Slack app to run search queries for you."); }}); ``` * * * ## Use logger {#use-logger} You can access [SLF4J](http://www.slf4j.org/) logger in `Context` objects. ``` app.command("/weather", (req, ctx) -> { String keyword = req.getPayload().getText(); String userId = req.getPayload().getUserId(); ctx.logger.info("Weather search by keyword: {} for user: {}", keyword, userId); return ctx.ack(weatherService.find(keyword).toMessage());}); ``` If you use the [ch.qos.logback:logback-classic](https://central.sonatype.com/artifact/ch.qos.logback/logback-classic) library as the implementation of the APIs, you can configure the settings by [logback.xml](http://logback.qos.ch/manual/configuration.html) etc. ``` %date %level [%thread] %logger{64} %msg%n ``` * * * ## Middleware {#middleware} Bolt offers chaining middleware supports. You can customize `App` behavior by weaving a kind of filter to all events. Here is an example demonstrating how it works. The middleware changes your app's behavior in error patterns only when `SLACK_APP_DEBUG_MODE` env variable exists. ``` import com.slack.api.bolt.App;import com.slack.api.bolt.response.Response;import com.slack.api.bolt.util.JsonOps;import java.util.Arrays;import static java.util.stream.Collectors.joining;class DebugResponseBody { String responseType; // ephemeral, in_channel String text;}String debugMode = System.getenv("SLACK_APP_DEBUG_MODE");App app = new App();if (debugMode != null && debugMode.equals("1")) { // enable only when SLACK_APP_DEBUG_MODE=1 app.use((req, _resp, chain) -> { Response resp = chain.next(req); if (resp.getStatusCode() != 200) { resp.getHeaders().put("content-type", Arrays.asList(resp.getContentType())); // dump all the headers as a single string String headers = resp.getHeaders().entrySet().stream() .map(e -> e.getKey() + ": " + e.getValue() + "\n").collect(joining()); // set an ephemeral message with useful information DebugResponseBody body = new DebugResponseBody(); body.responseType = "ephemeral"; body.text = ":warning: *[DEBUG MODE] Something is technically wrong* :warning:\n" + "Below is a response the Slack app was going to send...\n" + "*Status Code*: " + resp.getStatusCode() + "\n" + "*Headers*: ```" + headers + "```" + "\n" + "*Body*: ```" + resp.getBody() + "```"; resp.setBody(JsonOps.toJsonString(body)); resp.setStatusCode(200); } return resp; });} ``` The middleware transforms an unsuccessful response such as `404 Not Found` to a `200 OK` response with an ephemeral message that tells useful information for debugging. ![Bolt middleware ](/assets/images/bolt-middleware-84781417a8fd15095c3c5b3f0f26bb69.png) #### Order of execution in middleware list {#order-of-execution-in-middleware-list} A set of the built-in middleware precedes your custom middleware. So, if the app detects something in built-in ones and stops calling `chain.next(req)`, succeeding ones won't be executed. The most common would be the case where a request has been denied by `RequestVerification` middleware. After the denial, any middleware won't be executed, so that the above middleware also doesn't work for the case. #### Customize the built-in middleware list {#customize-the-built-in-middleware-list} Bolt turns the following middleware on by default: * [RequestVerification](https://github.com/slackapi/java-slack-sdk/blob/main/bolt/src/main/java/com/slack/api/bolt/middleware/builtin/RequestVerification.java) to verify the signature in HTTP Mode requests * [SingleTeamAuthorization](https://github.com/slackapi/java-slack-sdk/blob/main/bolt/src/main/java/com/slack/api/bolt/middleware/builtin/SingleTeamAuthorization.java) or [MultiTeamsAuthorization](https://github.com/slackapi/java-slack-sdk/blob/main/bolt/src/main/java/com/slack/api/bolt/middleware/builtin/MultiTeamsAuthorization.java) to look up the valid OAuth access token for handling a request * [IgnoringSelfEvents](https://github.com/slackapi/java-slack-sdk/blob/main/bolt/src/main/java/com/slack/api/bolt/middleware/builtin/IgnoringSelfEvents.java) to skip the events generated by this app's bot user (this is useful for avoiding code error causing an infinite loop) * [SSLCheck](https://github.com/slackapi/java-slack-sdk/blob/main/bolt/src/main/java/com/slack/api/bolt/middleware/builtin/SSLCheck.java) to handle `ssl_check=1` requests from Slack Although we generally do not recommend disabling these middleware as they are commonly necessary, you can disable them using the flags like `ignoringSelfEventsEnabled` in `AppConfig` objects. ``` AppConfig appConfig = new AppConfig();appConfig.setIgnoringSelfEventsEnabled(false); // the default is trueappConfig.setSslCheckEnabled(false); // the default is true// Please don't do this without an alternative solutionappConfig.setRequestVerificationEnabled(false); // the default is trueApp app = new App(appConfig); ``` Make sure it's safe enough when you turn a built-in middleware off. **We strongly recommend using `RequestVerification` for better security**. If you have a proxy that verifies a request signature in front of the Bolt app, you may disable `RequestVerification` to avoid duplication of work. Don't turn it off just for ease of development. * * * ## Supported web frameworks {#supported-web-frameworks} Refer to the [supported web frameworks page](/tools/java-slack-sdk/guides/supported-web-frameworks) for more details. * * * ## Deployments {#deployments} [View an example here](https://github.com/slackapi/java-slack-sdk/tree/main/bolt-google-cloud-functions-example/src/main/java/functions). --- Source: https://docs.slack.dev/tools/java-slack-sdk/guides/composing-messages # Composing Messages This section shows how to build Slack messages using the `slack-api-client` library. If you're not familiar with the [`chat.postMessage`](/reference/methods/chat.postMessage) API method yet, read [the Web API basics page](/tools/java-slack-sdk/guides/web-api-basics) before trying the samples here. Before jumping into Java code, we recommend developing an understanding of composing Slack messages. [Read the API documentation](/messaging/) for more information. * * * ## Text formatting {#text-formatting} Composing a text message is the simplest way to post a message to Slack conversations. ``` import com.slack.api.Slack;import com.slack.api.methods.response.chat.ChatPostMessageResponse;String channelId = "C1234567";String text = ":wave: Hi from a bot written in Java!";Slack slack = Slack.getInstance();String token = System.getenv("SLACK_TOKEN");ChatPostMessageResponse response = slack.methods(token).chatPostMessage(req -> req .channel(channelId) .text(text)); ``` Use `text` by providing a string value with a valid format. Consult [Basic formatting with `mrkdwn`](/messaging/formatting-message-text) for understanding the markup language. * * * ## Building blocks for rich message layouts {#building-blocks-for-rich-message-layouts} [Block Kit](/block-kit/) is a UI framework for Slack apps that offers a balance of control and flexibility when building experiences in messages and other [surfaces](/surfaces/). It may not be easy to compose a large JSON data structure in Java code, so we offer setter methods like `blocksAsString(String)` that accept a `blocks` argument as a single string value. This method is intended to be used with loaded external file data or outcomes by template engines. ``` ChatPostMessageResponse response = slack.methods(token).chatPostMessage(req -> req .channel("C1234567") .blocksAsString("[{\"type\": \"divider\"}]")); ``` This library also offers a type-safe way to build blocks like the one below. Just by having a few static imports, building blocks is much easier, safer, and more readable to everyone. ``` import static com.slack.api.model.block.Blocks.*;import static com.slack.api.model.block.composition.BlockCompositions.*;import static com.slack.api.model.block.element.BlockElements.*;ChatPostMessageResponse response = slack.methods(token).chatPostMessage(req -> req .channel("C1234567") .blocks(asBlocks( section(section -> section.text(markdownText("*Please select a restaurant:*"))), divider(), actions(actions -> actions .elements(asElements( button(b -> b.text(plainText(pt -> pt.emoji(true).text("Farmhouse"))).value("v1")), button(b -> b.text(plainText(pt -> pt.emoji(true).text("Kin Khao"))).value("v2")) )) ) ))); ``` You can build a message for incoming webhooks and `response_url` calls this way too. * * * ## Block Kit Kotlin DSL {#block-kit-kotlin-dsl} If you're using Kotlin, the Java Slack SDK also provides Kotlin-style builders which you can use to build your Block Kit structures. ``` import com.slack.api.Slackimport com.slack.api.model.block.Blocks.*import com.slack.api.methods.kotlin_extension.request.chat.blocksval slack = Slack.getInstance()val token = System.getenv("token")val response = slack.methods(token).chatPostMessage { req -> req .channel("C1234567") .blocks { section { // "text" fields can be constructed via `plainText()` and `markdownText()` markdownText("*Please select a restaurant:*") } divider() actions { // To align with the JSON structure, you could put the `elements { }` block around the buttons but for brevity it can be omitted // The same is true for things such as the section block's "accessory" container button { // For instances where only `plain_text` is acceptable, the field's name can be filled with `plain_text` inputs text("Farmhouse", emoji = true) value("v1") } button { text("Kin Khao", emoji = true) value("v2") } } }} ``` ### Installation {#installation} Add the Block Kit Kotlin DSL via 2 artifacts: artifactId Description [`com.slack.api:slack-api-model-kotlin-extension`](https://central.sonatype.com/artifact/com.slack.api/slack-api-model-kotlin-extension) [📖](https://oss.sonatype.org/service/local/repositories/releases/archive/com/slack/api/slack-api-model-kotlin-extension/1.49.0/slack-api-model-kotlin-extension-1.49.0-javadoc.jar/!/index.html#package) The `slack-api-model` Kotlin extension, which adds the Kotlin DSL itself as well as the standalone `withBlocks { }` builder and `View.ViewBuilder`'s `.blocks { }` extension function. [`com.slack.api:slack-api-client-kotlin-extension`](https://central.sonatype.com/artifact/com.slack.api/slack-api-client-kotlin-extension) [📖](https://oss.sonatype.org/service/local/repositories/releases/archive/com/slack/api/slack-api-client-kotlin-extension/1.49.0/slack-api-client-kotlin-extension-1.49.0-javadoc.jar/!/index.html#package) The `slack-api-client` Kotlin extension, which adds the `.blocks { }` extension function to `MethodsClient`'s request object builders for seamless use of the DSL with the Java builders such as `ChatPostEphemeralRequestBuilder`, `ChatPostMessageRequestBuilder`, `ChatScheduleMessageRequestBuilder`, and `ChatUpdateRequestBuilder`. #### Adding via Gradle {#adding-via-gradle} ``` dependencies { implementation "com.slack.api:slack-api-model-kotlin-extension:1.49.0" implementation "com.slack.api:slack-api-client-kotlin-extension:1.49.0"} ``` #### Adding via Gradle Kotlin DSL {#adding-via-gradle-kotlin-dsl} ``` dependencies { implementation("com.slack.api:slack-api-model-kotlin-extension:1.49.0") implementation("com.slack.api:slack-api-client-kotlin-extension:1.49.0")} ``` ### Notable examples and features {#notable-examples-and-features} Below are some code snippets demonstrating how to use this DSL. #### Standalone withBlocks { } builder {#standalone-withblocks---builder} This comes with the model extension. You can create lists of blocks outside of the `slack-api-client` Kotlin extension functions with the `withBlocks { }` builder. ``` import com.slack.api.model.kotlin_extension.block.withBlocksval blocks = withBlocks { section { plainText("Now this can be passed to anything that requires a list of LayoutBlocks") }} ``` #### Type safe enums for inputs {#type-safe-enums-for-inputs} This requires specific string inputs. Type-safe enums are available for properties of some block elements which require specific input strings. With this, you get the benefit of verifying that your inputs are correct at compile time, and you gain access to Kotlin enum features such as being able to iterate over or retrieve all possible values for these inputs. Versions of these inputs which accept strings are also available, if you prefer. ``` import com.slack.api.model.kotlin_extension.block.element.ButtonStyleimport com.slack.api.model.kotlin_extension.block.element.ConversationTypeimport com.slack.api.model.kotlin_extension.block.withBlocksval blocks = withBlocks { section { plainText("Please select the person or group you would like to send a cat GIF to.") // "accessory" is provided here, but it can be omitted for brevity accessory { conversationsSelect { // Or alternatively, provide strings via `filter("im", "mpim")` if you'd prefer filter(ConversationType.IM, ConversationType.MULTIPARTY_IM) placeholder("Where should we send the cat?") confirm { title("Confirm destination") plainText("Are you sure you want to send a cat GIF to this person or group?") confirm("Yes, send it") deny("Don't send it") style(ButtonStyle.PRIMARY) } } } }} ``` #### Write DSL extension functions for message templating {#write-dsl-extension-functions-for-message-templating} Because it is a Kotlin DSL, you benefit from Kotlin language features while you are constructing your messages, one of which is being able to create extension functions which reproduce common Block Kit structures. This makes your code less repetitive and easier to read. You also benefit from being able to use conditionals and loops as you construct your blocks. ``` import com.slack.api.model.kotlin_extension.block.ActionsBlockBuilderimport com.slack.api.model.kotlin_extension.block.withBlocksfun ActionsBlockBuilder.presentOptions(vararg optionNames: String, prompt: String? = null) { staticSelect { if (prompt != null) { placeholder(prompt) } options { for (optionName in optionNames) { option { plainText(optionName) value(optionName.toLowerCase()) } } } }}val blocks = withBlocks { section { markdownText("Please select your favorite color.") } actions { presentOptions( "Green", "Red", "Blue", "Yellow", "Orange", "Black", prompt = "Pick a color..." ) }} ``` --- Source: https://docs.slack.dev/tools/java-slack-sdk/guides/custom-steps # Custom Steps Your app can use the `function()` method to listen to incoming [custom step requests](/workflows/workflow-steps). Custom steps are used in Workflow Builder to build workflows. The method requires a step `callback_id` of type `string`. This `callback_id` must also be defined in your [Function](/reference/app-manifest#functions) definition. Custom steps must be finalized using the `complete()` or `fail()` listener arguments to notify Slack that your app has processed the request. * `complete()` requires one argument: an `outputs` object. It ends your custom step successfully and provides an object containing the outputs of your custom step as per its definition. * `fail()` requires one argument: `error` of type `string`. It ends your custom step unsuccessfully and provides a message containing information regarding why your custom step failed. You can reference your custom step's inputs using the `getInputs()` method shown below. ``` app.function("sample_function", (req, ctx) -> { app.executorService().submit(() -> { try { String userId = req.getEvent().getInputs().get("user_id").asString(); ChatPostMessageResponse response = ctx.client().chatPostMessage(r -> r .channel(userId) // sending a DM .text("Hi! Thank you for submitting the request! We'll let you know once the processing completes.") ); Map outputs = new HashMap(); outputs.put("channel_id", response.getChannel()); outputs.put("ts", response.getTs()); ctx.complete(outputs); } catch (Exception e) { ctx.logger.error(e.getMessage(), e); try { ctx.fail("Failed to handle 'sample_function' custom step execution (error: " + e.getMessage() + ")"); } catch (Exception ex) { ctx.logger.error(e.getMessage(), e); } } }); return ctx.ack();}); ``` The corresponding function definition section of the app manifest for the preceding function might look like this: ``` ..."functions": { "sample_function": { "title": "Send a request", "description": "Send some request to the backend", "input_parameters": { "user_id": { "type": "slack#/types/user_id", "title": "User", "description": "Who to send it", "is_required": true, "hint": "Select a user in the workspace", "name": "user_id" } }, "output_parameters": { "channel_id": { "type": "slack#/types/channel_id", "title": "DM ID", "description": "The DM ID", "is_required": true, "name": "channel_id" }, "ts": { "type": "string", "title": "Message timestamp", "description": "Sent message timestamp", "is_required": true, "name": "ts" } } }} ``` Once your custom step is defined in your app's manifest and implemented in code, it is discoverable in Workflow Builder when you **Add Step** and search for the title of your step or name of your app. --- Source: https://docs.slack.dev/tools/java-slack-sdk/guides/events-api # Events The [Events API](/apis/events-api/) is a streamlined way to build apps and bots that respond to activities in Slack. All you need is a Slack app and a secure place for us to send your events. ### Slack app configuration {#slack-app-configuration} To enable the Events API, visit the [Slack app settings page](http://api.slack.com/apps), choose the app you're working on, and go to **Features** > **Event Subscriptions** on the left pane. There are a few things to do on the page. * Turn on **Enable Events** * Set the **Request URL** to `https://{your app's public URL domain}/slack/events` (this step is not required for Socket Mode apps) * Add subscriptions to bot events * Click **Subscribe to bot events** * Click **Add Bot User Event** button * Choose events to subscribe to * Click the **Save Changes** button at the bottom for sure ### What your Bolt app does {#what-your-bolt-app-does} To handle Events API requests, do the following: 1. [Verify requests](/authentication/verifying-requests-from-slack) from Slack 2. Parse the request body and check if the `type` in `event` is the one you'd like to handle 3. Code the desired logic you want to do with the event data 4. Respond to the Slack API server with `200 OK` as an acknowledgment Your app has to respond to the request within 3 seconds by `ack()` method. Otherwise, the Slack Platform may retry. * * * ## Examples {#examples} Tip If you're a beginner to using Bolt for Slack App development, consult [Getting Started with Bolt](/tools/java-slack-sdk/guides/getting-started-with-bolt) first. Bolt does many of the commonly required tasks for you. The steps you need to handle are: * Specify [the Java class](https://oss.sonatype.org/service/local/repositories/releases/archive/com/slack/api/slack-api-model/1.49.0/slack-api-model-1.49.0-javadoc.jar/!/com/slack/api/model/event/Event.html) corresponding to `event.type` (and also `event.subtype` [when necessary](/reference/events/message)) to handle * Code the desired logic you want to do with the event data * Call `ack()` as an acknowledgment In event payloads, `response_url` is not included as it's not a payload coming from direct user interactions. Also, it's not possible to post a message using `ctx.ack()` for the same reason. If an event you receive is a user interaction and you'd like to post a reply to the user in the conversation where the event happened, call the [`chat.postMessage`](/reference/methods/chat.postMessage) method with `channel` in the event payload. ``` import com.slack.api.methods.response.chat.ChatPostMessageResponse;import com.slack.api.model.event.ReactionAddedEvent;app.event(ReactionAddedEvent.class, (payload, ctx) -> { ReactionAddedEvent event = payload.getEvent(); if (event.getReaction().equals("white_check_mark")) { ChatPostMessageResponse message = ctx.client().chatPostMessage(r -> r .channel(event.getItem().getChannel()) .threadTs(event.getItem().getTs()) .text("<@" + event.getUser() + "> Thank you! We greatly appreciate your efforts :two_hearts:")); if (!message.isOk()) { ctx.logger.error("chat.postMessage failed: {}", message.getError()); } } return ctx.ack();}); ``` The same code in Kotlin looks as below. (New to Kotlin? [Getting Started in Kotlin](/tools/java-slack-sdk/guides/getting-started-with-bolt#getting-started-in-kotlin) may be helpful.) ``` app.event(ReactionAddedEvent::class.java) { payload, ctx -> val event = payload.event if (event.reaction == "white_check_mark") { val message = ctx.client().chatPostMessage { it.channel(event.item.channel) .threadTs(event.item.ts) .text("<@${event.user}> Thank you! We greatly appreciate your efforts :two_hearts:") } if (!message.isOk) { ctx.logger.error("chat.postMessage failed: ${message.error}") } } ctx.ack()} ``` Here is another example. With an `app.message` listener, you can receive only the events that contains given keyword or regular expressions, and do something with those event data in a fewer lines of code. ``` import com.slack.api.methods.MethodsClient;import com.slack.api.methods.response.chat.ChatGetPermalinkResponse;import com.slack.api.methods.response.chat.ChatPostMessageResponse;import com.slack.api.methods.response.reactions.ReactionsAddResponse;import com.slack.api.model.event.MessageEvent;import java.util.Arrays;import java.util.regex.Pattern;String notificationChannelId = "D1234567";// check if the message contains some monitoring keywordsPattern sdk = Pattern.compile(".*[(Java SDK)|(Bolt)|(slack\\-java\\-sdk)].*", Pattern.CASE_INSENSITIVE);app.message(sdk, (payload, ctx) -> { MessageEvent event = payload.getEvent(); String text = event.getText(); MethodsClient client = ctx.client(); // Add 👀reacji to the message String channelId = event.getChannel(); String ts = event.getTs(); ReactionsAddResponse reaction = client.reactionsAdd(r -> r.channel(channelId).timestamp(ts).name("eyes")); if (!reaction.isOk()) { ctx.logger.error("reactions.add failed: {}", reaction.getError()); } // Send the message to the SDK author ChatGetPermalinkResponse permalink = client.chatGetPermalink(r -> r.channel(channelId).messageTs(ts)); if (permalink.isOk()) { ChatPostMessageResponse message = client.chatPostMessage(r -> r .channel(notificationChannelId) .text("The Java SDK might be mentioned:\n" + permalink.getPermalink()) .unfurlLinks(true)); if (!message.isOk()) { ctx.logger.error("chat.postMessage failed: {}", message.getError()); } } else { ctx.logger.error("chat.getPermalink failed: {}", permalink.getError()); } return ctx.ack();}); ``` If matching an exact word in a text message works for you, the code looks much simpler as below. ``` app.message(":wave:", (payload, ctx) -> { ctx.say("Hello, <@" + payload.getEvent().getUser() + ">"); return ctx.ack();}); ``` ## Under the hood {#under-the-hood} If you hope to understand what is happening with the above code, reading the following (pseudo) code may be helpful. ``` import java.util.Map;import com.google.gson.Gson;import com.slack.api.Slack;import com.slack.api.app_backend.events.*;import com.slack.api.app_backend.events.payload.MessagePayload;import com.slack.api.util.json.GsonFactory;PseudoHttpResponse handle(PseudoHttpRequest request) { // 1. Verify requests from Slack // /authentication/verifying-requests-from-slack // This needs "X-Slack-Signature" header, "X-Slack-Request-Timestamp" header, and raw request body if (!PseudoSlackRequestVerifier.isValid(request)) { return PseudoHttpResponse.builder().status(401).build(); } // 2. Parse the request body and check if the `type` in `event` is the one you'd like to handle // The request body in JSON format String payloadString = request.getBodyAsString(); EventTypeExtractor eventTypeExtractor = new EventsDispatcherImpl(); String eventType = eventTypeExtractor.extractEventType(payloadString); if (eventType != null && eventType.equals("message")) { Gson gson = GsonFactory.createSnakeCase(); MessagePayload payload = gson.fromJson(payloadString, MessagePayload.class); // 3. Whatever you want to do with the event data } else { // other patterns return PseudoHttpResponse.builder().status(404).build(); } // 4. Respond to the Slack API server with 200 OK as an acknowledgment return PseudoHttpResponse.builder().status(200).build();} ``` --- Source: https://docs.slack.dev/tools/java-slack-sdk/guides/getting-started-with-bolt # Getting Started with Bolt for Java **Bolt for Java** is a framework on the JVM that offers an abstraction layer to build Slack apps using modern platform features. This guide explains how to start your first Bolt app. If you're not yet familiar with Slack app development in general, we recommend reading the [API docs](/). * * * ## Setting up your project {#project-setup} Let's start building a Slack app using Bolt! This guide includes instructions on how to set up a Bolt project with Maven and Gradle, so use whichever section you'd like. ### Using Maven {#maven} After you [create your Maven project](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html), you need to add the `bolt` dependency to your `pom.xml` file. The `bolt` dependency is a framework-agnostic module. If you use Bolt along with [Spring Boot](https://spring.io/projects/spring-boot), [Quarkus (Undertow)](https://quarkus.io/), or any others on top of the Servlet environment, the `bolt-servlet` library is required for your app. Adding only `bolt-servlet` also works. * Socket Mode * HTTP To enable [Socket Mode](/apis/events-api/using-socket-mode), the `bolt-socket-mode` library and its provided-scope dependencies are also required for your app. ``` com.slack.api bolt 1.49.0 com.slack.api bolt-socket-mode 1.49.0 javax.websocket javax.websocket-api 1.1 org.glassfish.tyrus.bundles tyrus-standalone-client 1.20 org.slf4j slf4j-simple 1.7.36 ``` By default, the `tyrus-standalone-client` dependency is used as the implementation to manage socket connections in the `bolt-socket-mode` artifact. If instead you would prefer to use the `Java-WebSocket` implementation, swap its artifact in instead of `tyrus-standalone-client`, and then set `SocketModeClient.Backend.JavaWebSocket` when initializing the client instance: ``` org.java-websocket Java-WebSocket 1.5.1 ``` You will also need to ensure you set the compiler source and target to at least 1.8: ``` 1.8 1.8 ``` ``` com.slack.api bolt 1.49.0 com.slack.api bolt-servlet 1.49.0 org.slf4j slf4j-simple 1.7.36 ``` If you run the Bolt app on the Jetty HTTP server without any frameworks, you can simply go with `bolt-jetty` module. If you prefer using the latest Jetty server, which is compatible with [Jakarta EE Servlet APIs](https://jakarta.ee/specifications/servlet/5.0/), `bolt-jakarta-jetty` is available for you. ``` com.slack.api bolt-jetty 1.49.0 ``` You will also need to ensure you set the compiler source and target to at least 1.8: ``` 1.8 1.8 ``` ### Using Gradle {#gradle} After you [create your Gradle project](https://docs.gradle.org/current/samples/sample_building_java_applications.html), add the `bolt` dependencies to `build.gradle`. * Socket Mode * HTTP ``` dependencies { implementation("com.slack.api:bolt-socket-mode:1.49.0") implementation("javax.websocket:javax.websocket-api:1.1") implementation("org.glassfish.tyrus.bundles:tyrus-standalone-client:1.20") implementation("org.slf4j:slf4j-simple:1.7.36")} ``` ``` dependencies { implementation("com.slack.api:bolt:1.49.0") implementation("com.slack.api:bolt-servlet:1.49.0") implementation("com.slack.api:bolt-jetty:1.49.0") implementation("org.slf4j:slf4j-simple:1.7.36")} ``` * * * ## Running your Bolt app {#running} * Socket Mode * HTTP **Using `bolt-socket-mode`** `bolt-socket-mode` is a handy way to start your [Socket Mode](/apis/events-api/using-socket-mode) app. It allows developers to build a Slack app backend service by writing only a main method initializes `App` and establishes a WebSocket connection to the Socket Mode servers. **Using `bolt-jetty`** `bolt-jetty` is a handy way to start your Slack app server. It allows developers to build a Slack app backend service by writing only a main method that initializes `App` and starts an HTTP server. **Using `build.gradle`** The following build settings should work as-is. Put it in the root directory of your project. * Socket Mode * HTTP ``` plugins { id("application")}repositories { mavenCentral()}dependencies { implementation("com.slack.api:bolt-socket-mode:1.49.0") implementation("javax.websocket:javax.websocket-api:1.1") implementation("org.glassfish.tyrus.bundles:tyrus-standalone-client:1.20") implementation("org.slf4j:slf4j-simple:1.7.36")}application { mainClassName = "hello.MyApp"}run { // gradle run -DslackLogLevel=debug systemProperty "org.slf4j.simpleLogger.log.com.slack.api", System.getProperty("slackLogLevel")} ``` ``` plugins { id("application")}repositories { mavenCentral()}dependencies { implementation("com.slack.api:bolt-jetty:1.49.0") implementation("org.slf4j:slf4j-simple:1.7.36")}application { mainClassName = "hello.MyApp"}run { // gradle run -DslackLogLevel=debug systemProperty "org.slf4j.simpleLogger.log.com.slack.api", System.getProperty("slackLogLevel")} ``` **Using `src/main/java/hello/MyApp.java`** * Socket Mode * HTTP Only single source code is required to run your first Bolt app. You'll need to define the main method that starts `SocketModeApp`. ``` package hello;import com.slack.api.bolt.App;import com.slack.api.bolt.socket_mode.SocketModeApp;public class MyApp { public static void main(String[] args) throws Exception { // App expects an env variable: SLACK_BOT_TOKEN App app = new App(); app.command("/hello", (req, ctx) -> { return ctx.ack(":wave: Hello!"); }); // SocketModeApp expects an env variable: SLACK_APP_TOKEN new SocketModeApp(app).start(); }} ``` If you go with JDK 10+, thanks to [Local Variable Type Inference](https://docs.oracle.com/en/java/javase/21/language/local-variable-type-inference.html), you can write more concise code. To do so, install OpenJDK 11 and set the compatible Java versions in `build.gradle` as below, configuring the same on your IDE. ``` java { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11} ``` Now, you don't need to repeat the same type in a single line. ``` var app = new App();app.command("/hello", (req, ctx) -> { return ctx.ack(":wave: Hello!");});new SocketModeApp(app).start(); ``` Only single source code is required to run your first Bolt app. You'll need to define the main method that starts `SlackAppServer`. Your server with the default configuration will listen to the 3000 port but it's configurable. Check other constructors of the class to customize the behavior. ``` package hello;import com.slack.api.bolt.App;// If you use bolt-jakarta-jetty, you can import `com.slack.api.bolt.jakarta_jetty.SlackAppServer` insteadimport com.slack.api.bolt.jetty.SlackAppServer;public class MyApp { public static void main(String[] args) throws Exception { // App expects env variables (SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET) App app = new App(); app.command("/hello", (req, ctx) -> { return ctx.ack(":wave: Hello!"); }); SlackAppServer server = new SlackAppServer(app); server.start(); // http://localhost:3000/slack/events }} ``` If you go with JDK 10+, thanks to [Local Variable Type Inference](https://docs.oracle.com/en/java/javase/21/language/local-variable-type-inference.html), you can write more concise code. To do so, install OpenJDK 11 and set the compatible Java versions in `build.gradle` as below, configuring the same on your IDE. ``` java { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11} ``` Now, you don't need to repeat the same type in a single line. ``` var app = new App();app.command("/hello", (req, ctx) -> { return ctx.ack(":wave: Hello!");});var server = new SlackAppServer(app);server.start(); ``` ### Environment variables {#env-variables} The default constructor expects the following two environment variables to exist when starting the app. Env Variable Description `SLACK_BOT_TOKEN` The valid bot token value starting with `xoxb-` in your development workspace. To issue a bot token, install your Slack app that has a bot user to your development workspace. Visit the [Slack app settings page](http://api.slack.com/apps), choose the app you're working on, and go to **Settings** > **Install App** on the left pane (Add [`app_mentions:read`](/reference/scopes/app_mentions.read) bot scope if you see the message saying "Please add at least one feature or permission scope to install your app."). If you run an app that is installable for multiple workspaces, no need to specify this. Consult [App Distribution (OAuth)](/tools/java-slack-sdk/guides/app-distribution) for further information. `SLACK_SIGNING_SECRET` The secret value shared only with the Slack Platform. It is used for verifying incoming requests from Slack. Request verification is crucial for security as Slack apps have internet-facing endpoints. To know the value, visit the [Slack app settings page](http://api.slack.com/apps), choose the app you're working on, go to **Settings** > **Basic Information** on the left pane, and find **App Credentials** > **Signing Secret** on the page. Refer to [Verifying requests from Slack](/authentication/verifying-requests-from-slack) for more information. If you prefer configuring an `App` in a different way, write some code to initialize `AppConfig` on your own. Set the two environment variables and run: * Gradle users: `gradle run` (for more detailed logging: `gradle run -DslackLogLevel=debug`), * Maven users: `mvn compile exec:java -Dexec.mainClass="hello.MyApp"` (for more detailed logging you can also provide the `-Dorg.slf4j.simpleLogger.defaultLogLevel=debug` flag) The command runs your main method. ``` # Visit https://api.slack.com/apps to know theseexport SLACK_BOT_TOKEN=xoxb-...your-own-valid-oneexport SLACK_SIGNING_SECRET=123abc...your-own-valid-one# run the main function# gradle users should run:gradle run# maven users should run:mvn compile exec:java -Dexec.mainClass="hello.MyApp" ``` You will see the message saying "**⚡️ Bolt app is running!**" in `stdout`. If you get stuck, go through the following checklist: * Socket Mode * HTTP * ✅ JDK 8 or higher installed (if not, run `brew install openjdk@11` for macOS / visit [OpenJDK website](https://openjdk.java.net/install/) for others) * ✅ Gradle installed (if not, run `brew install gradle` for macOS / visit [their website](https://gradle.org/) for others) * ✅ `build.gradle` has `bolt-socket-mode` and `tyrus-standalone-client` in the dependencies and valid application plugin settings * ✅ `src/main/java/hello/MyApp.java` with a class having its main method * ✅ [Create a Slack App](https://api.slack.com/apps?new_app=1), add [`commands`](/reference/scopes/commands) bot scope, add **an app-level token with `connections:write` scope**, and install the app to your development workspace * ✅ Copy [**Bot User OAuth Access Token**](/authentication/tokens#bot) and [**App-Level Token**](/authentication/tokens#app-level) from [your Slack App admin pages](https://api.slack.com/apps) and set them to env variables * ✅ JDK 8 or higher installed (if not, run `brew install openjdk@11` for macOS / visit [OpenJDK website](https://openjdk.java.net/install/) for others) * ✅ Gradle installed (if not, run `brew install gradle` for macOS / visit [their website](https://gradle.org/) for others) * ✅ `build.gradle` has `bolt-jetty` dependency and valid application plugin settings * ✅ `src/main/java/hello/MyApp.java` with a class having its main method * ✅ [Create a Slack App](https://api.slack.com/apps?new_app=1), add [`app_mentions:read`](/reference/scopes/app_mentions.read) bot scope, install the app to your development workspace * ✅ Copy [**Bot User OAuth Access Token**](/authentication/tokens#bot) and [**Signing Secret**](/authentication/verifying-requests-from-slack) from [your Slack App admin pages](https://api.slack.com/apps) and set them to env variables ### Enabling the /hello command {#hello} Your app is up now! However, the slash command `/hello` in the code is still unavailable. To enable it, follow the steps below: * Socket Mode * HTTP * Visit [Slack app settings pages](https://api.slack.com/apps) * Choose your app * Go to **Settings** > **Socket Mode** on the left pane * Turn on **Enable Socket Mode** * Go to **Features** > **Slash Commands** on the left pane * Click **Create New Command** button * Input the command information on the dialog: * **Command**: `/hello` * **Short Description**: whatever you like * Click **Save** Button * Configure a way to allow the Slack API server to access your Bolt app * A well-known way is to use [ngrok](https://ngrok.com/) - install it and run `ngrok http 3000` on another terminal * Configure & Reinstall the Slack app * Visit [Slack app settings pages](https://api.slack.com/apps) * Choose your app, go to **Features** > **Slash Commands** on the left pane * Click **Create New Command** button * Input the command information on the dialog: * **Command**: `/hello` * **Request URL**: `https://{your domain here}/slack/events` - if you use ngrok for development, the URL would be `https://{random}.ngrok.io/slack/events` * **Short Description**: whatever you like * Click **Save** Button * Go to **Settings** > **Install App** and click **Reinstall App** button Now you can hit the `/hello` command in your development workspace. If your app is successfully running, the app should respond to the command by replying `👋 Hello!`. ### What about Spring Boot? {#spring-boot} As [Spring Boot](https://spring.io/projects/spring-boot) is one of the most popular web frameworks in the Java world, you may be curious about the possibility to let this Bolt app live together with it. Don't worry, we can _inject_ Bolt into Spring Boot apps. Add `implementation("com.slack.api:bolt:1.49.0")` to `dependencies` in `build.gradle` and write a few lines of code. ``` @Configurationpublic class SlackApp { @Bean public App initSlackApp() { App app = new App(); app.command("/hello", (req, ctx) -> ctx.ack("Hi there!")); return app; }}@WebServlet("/slack/events")public class SlackAppController extends SlackAppServlet { public SlackAppController(App app) { super(app); }} ``` Check [the detailed guide here](/tools/java-slack-sdk/guides/supported-web-frameworks) for more information. * * * ## Getting started with Kotlin {#getting-started-in-kotlin} For code simplicity, [Kotlin](https://kotlinlang.org/) is a great option for writing Bolt apps. In this section, you'll learn how to set up a Kotlin project for Bolt apps. **Using `build.gradle`** * Socket Mode * HTTP Most of the build settings are necessary for enabling Kotlin language. Adding `bolt-socket-mode` && `tyrus-standalone-client` to the dependencies is the only one that is specific to Bolt. ``` plugins { id("org.jetbrains.kotlin.jvm") version "1.7.21" id("application")}repositories { mavenCentral()}dependencies { implementation(platform("org.jetbrains.kotlin:kotlin-bom")) implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("com.slack.api:bolt-socket-mode:1.49.0") implementation("javax.websocket:javax.websocket-api:1.1") implementation("org.glassfish.tyrus.bundles:tyrus-standalone-client:1.20") implementation("org.slf4j:slf4j-simple:1.7.36") // or logback-classic}application { mainClassName = "MyAppKt" // add "Kt" suffix for main function source file} ``` Most of the build settings are necessary for enabling Kotlin language. Adding `bolt-jetty` dependency is the only one that is specific to Bolt. ``` plugins { id("org.jetbrains.kotlin.jvm") version "1.7.21" id("application")}repositories { mavenCentral()}dependencies { implementation(platform("org.jetbrains.kotlin:kotlin-bom")) implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("com.slack.api:bolt-jetty:1.49.0") implementation("org.slf4j:slf4j-simple:1.7.36") // or logback-classic}application { mainClassName = "MyAppKt" // add "Kt" suffix for main function source file} ``` If you're already familiar with Kotlin and prefer the Gradle Kotlin DSL, of course, there is nothing stopping you. **Using `src/main/kotlin/MyApp.kt`** Here is a minimum source file that starts a Bolt app on your local machine. * Socket Mode * HTTP ``` import com.slack.api.bolt.Appimport com.slack.api.bolt.socket_mode.SocketModeAppfun main() { val app = App() // Write some code here SocketModeApp(app).start()} ``` ``` import com.slack.api.bolt.Appimport com.slack.api.bolt.jetty.SlackAppServerfun main() { val app = App() // Write some code here val server = SlackAppServer(app) server.start() // http://localhost:3000/slack/events} ``` ### Running your Kotlin app {#run-kotlin} If all items from the checklist are ✅, bootstrapping your first Kotlin-flavored Bolt app will succeed: ``` # Visit https://api.slack.com/apps to know theseexport SLACK_BOT_TOKEN=xoxb-...your-own-valid-oneexport SLACK_SIGNING_SECRET=123abc...your-own-valid-one# run the main functiongradle run ``` From here, you're ready to write code and restart the app. Enjoy Bolt app development in Kotlin! 👋 Tip We strongly recommend using [IntelliJ IDEA](https://www.jetbrains.com/idea/) here even if you don't prefer using IDEs. The IDE is the smoothest way to try Kotlin application development. * * * ## Next steps {#next-steps} Read the [Bolt Basics](/tools/java-slack-sdk/guides/bolt-basics) page for further information. If you want to know ways to run a Bolt app with Spring Boot, Micronaut, Quarkus, or Helidon SE, refer to the [Supported Web Frameworks](/tools/java-slack-sdk/guides/supported-web-frameworks) page. Also, many examples are available in the GitHub repository. * [Example apps with Spring Boot](https://github.com/slackapi/java-slack-sdk/tree/main/bolt-spring-boot-examples) * [Example apps with Micronaut](https://github.com/slackapi/java-slack-sdk/tree/main/bolt-micronaut/src/test/java/example) * [Example apps with Quarkus](https://github.com/slackapi/java-slack-sdk/tree/main/bolt-quarkus-examples) * [Example apps in Kotlin](https://github.com/slackapi/java-slack-sdk/tree/main/bolt-kotlin-examples) * [Example apps with Docker](https://github.com/slackapi/java-slack-sdk/tree/main/bolt-docker-examples) --- Source: https://docs.slack.dev/tools/java-slack-sdk/guides/incoming-webhooks # Incoming webhooks [Incoming webhooks](/messaging/sending-messages-using-incoming-webhooks) are a straightforward way to post messages from apps into Slack. Creating an incoming webhook gives you a unique URL to which you send a JSON payload with the message and some options. ### Slack app configuration {#slack-app-configuration} To enable this feature, visit the [Slack app settings page](http://api.slack.com/apps), choose the app you're working on, go to **Features** > **Incoming Webhooks** on the left pane, and then turn on **Activate Incoming Webhooks**. Then, install the app to your development workspace. Each time your app is installed, a new webhook URL will be generated. * * * ## How to use {#how-to-use} Here is a curl command example demonstrating how to send a message via an incoming webhook URL. ``` curl -X POST \ -H 'Content-type: application/json' \ -d '{"text":"Hello, World!"}' \ https://hooks.slack.com/services/T1234567/AAAAAAAA/ZZZZZZ ``` There are two ways to send a payload via incoming webhook with the Slack SDK for Java. ### Build a string payload {#build-a-string-payload} A primitive way is to build a payload as a single string. This method is nearly the same as running a curl command. Tip As with tokens, we don't recommend embedding a webhook URL in your source code. Consider using environment variables or some other secure way to store them. ``` import com.slack.api.Slack;import com.slack.api.webhook.WebhookResponse;Slack slack = Slack.getInstance();String webhookUrl = System.getenv("SLACK_WEBHOOK_URL"); // https://hooks.slack.com/services/T1234567/AAAAAAAA/ZZZZZZString payload = "{\"text\":\"Hello, World!\"}";WebhookResponse response = slack.send(webhookUrl, payload);System.out.println(response); // WebhookResponse(code=200, message=OK, body=ok) ``` If the URL is invalid or no longer available, you'll receive responses as below. ``` WebhookResponse(code=404, message=Not Found, body=no_team) ``` The response consists of its HTTP status code/message and a simple plain text body telling an error code. The `send` method may throw a `java.io.IOException` when having connectivity issues. ### Send using a payload object {#send-using-a-payload-object} Another way to send a payload is to build a Java object representing a webhook payload. ``` import com.slack.api.Slack;import com.slack.api.webhook.Payload;import com.slack.api.webhook.WebhookResponse;Slack slack = Slack.getInstance();String webhookUrl = System.getenv("SLACK_WEBHOOK_URL"); // https://hooks.slack.com/services/T1234567/AAAAAAAA/ZZZZZZPayload payload = Payload.builder().text("Hello, World!").build();WebhookResponse response = slack.send(webhookUrl, payload);System.out.println(response); // WebhookResponse(code=200, message=OK, body=ok) ``` To make it a bit more concise, using `WebhookPayloads.payload(function)` may look convenient to some. ``` import static com.slack.api.webhook.WebhookPayloads.*;WebhookResponse response = slack.send(webhookUrl, payload(p -> p.text("Hello, World!"))); ``` As we learned in [Composing Messages](/tools/java-slack-sdk/guides/composing-messages), using static methods for building blocks is useful. ``` import static com.slack.api.webhook.WebhookPayloads.*;import static com.slack.api.model.block.Blocks.*;import static com.slack.api.model.block.composition.BlockCompositions.*;import static com.slack.api.model.block.element.BlockElements.*;WebhookResponse response = slack.send(webhookUrl, payload(p -> p .text("Slack couldn't properly display the message.") .blocks(asBlocks( section(section -> section.text(markdownText("*Please select a restaurant:*"))), divider(), actions(actions -> actions .elements(asElements( button(b -> b.text(plainText(pt -> pt.emoji(true).text("Farmhouse"))).value("v1")), button(b -> b.text(plainText(pt -> pt.emoji(true).text("Kin Khao"))).value("v2")) )) ) )))); ``` --- Source: https://docs.slack.dev/tools/java-slack-sdk/guides/interactive-components # Interactive Components [Interactive components](/block-kit/#making-things-interactive) are a subset of [Block Kit](/block-kit/) elements that add interactivity to various [surfaces](/surfaces/). Interactions on blocks may happen in messages, [modals](/tools/java-slack-sdk/guides/modals), and [Home tabs](/tools/java-slack-sdk/guides/app-home). See [Composing messages](/tools/java-slack-sdk/guides/composing-messages) to learn how to build [Block Kit](/surfaces/) messages with this SDK. ### Slack app configuration {#slack-app-configuration} To enable interactive components, visit the [Slack app settings page](http://api.slack.com/apps), choose the app you're working on, and go to **Features** > **Interactivity & Shortcuts** on the left pane. There are three things to do on the page. * Turn on the feature * Set the **Request URL** to `https://{your app's public URL domain}/slack/events` (this step is not required for Socket Mode apps) * Click the **Save Changes** button at the bottom ### What your bolt app does {#what-your-bolt-app-does} To handle Slack requests by user interactions: 1. [Verify requests](/authentication/verifying-requests-from-slack) from Slack 2. Parse the request body and check if the `action_id` in a block is the one you'd like to handle 3. Build a reply message or surface to interact with the user further 4. Respond to the Slack API server with `200 OK` as an acknowledgment Your app has to respond to the request within 3 seconds by `ack()` method. Otherwise, the user will see the timeout error on Slack. For some of the requests including external selects, having valid parameters to the method may be required. * * * ## Examples {#examples} Tip If you're a beginner to using Bolt for Slack app development, consult [Getting Started with Bolt](/tools/java-slack-sdk/guides/getting-started-with-bolt) first. Bolt does many of the commonly required tasks for you. The steps you need to handle are: * Specify the `action_id` to handle (by either of the exact name or regular expression) * Build a reply message or surface to interact with the user further * Call `ack()` as an acknowledgment The request payloads have a `response_url`, so that your app can reply to the action (even asynchronously after the acknowledgment). The URL is usable up to 5 times within 30 minutes of the action invocation. If you post a message using `response_url`, call `ctx.ack()` without arguments and use `ctx.respond()` to post a message. Let's say, a message has a simple `"actions"`\-typed block that has a button. ``` { "type": "actions", "elements": [{ "type": "button", "action_id": "button-action", "text": { "type": "plain_text", "text": "Button", "emoji": true }, "value": "button's value" }]} ``` When a user clicks the button, `"block_actions"`\-typed request with the `action_id` the element with the value `"button-action"` has will come to your Bolt app. ``` // when a user clicks a button in the actions blockapp.blockAction("button-action", (req, ctx) -> { String value = req.getPayload().getActions().get(0).getValue(); // "button's value" if (req.getPayload().getResponseUrl() != null) { // Post a message to the same channel if it's a block in a message ctx.respond("You've sent " + value + " by clicking the button!"); } return ctx.ack();}); ``` The sample code in Kotlin looks like as below. ``` app.blockAction("button-action") { req, ctx -> val value = req.payload.actions[0].value if (req.payload.responseUrl != null) { ctx.respond("You've sent ${value} by clicking the button!") } ctx.ack()} ``` Here is another example. This is a [select menu using external data source](/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select). ``` { "block_id": "topics", "type": "section", "text": { "type": "mrkdwn", "text": "Select the meeting topics" }, "accessory": { "action_id": "topics-action", "type": "multi_external_select", "min_query_length": 1, "placeholder": { "type": "plain_text", "text": "Select", "emoji": true } }} ``` With this type of select menu, your app is expected to handle both `"block_suggestion"` and `"block_actions"` coming from the element named `"topics-action"`. ``` import com.slack.api.app_backend.interactive_components.response.Option;import com.slack.api.model.block.composition.PlainTextObject;import static com.slack.api.model.block.composition.BlockCompositions.plainText;import java.util.Arrays;import java.util.List;import static java.util.stream.Collectors.toList;final List