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 classThe 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.
import com.slack.api.bolt.App;
App app = new App();
app.command("/echo", (req, ctx) -> {
return ctx.ack(req.getText());
});
Dispatching events
Here is the list of the available methods to dispatch events.
Method Constraints (value: type) Descriptionapp.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: 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: 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: 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\<Event\>
Events API: Responds to any of bot/user events you subscribe to. app.function
callback_id: String
| Pattern
Custom steps: Defines a function that can be used as a custom step in Workflow Builder. app.globalShortcut
callback_id: String
| Pattern
Shortcuts: Responds to global shortcut invocations. app.message
keyword: String
| Pattern
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: Responds to shortcut invocations in message menus. app.viewClosed
callback_id: String
| Pattern
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: Responds to data submissions in modals. Development guides by feature
On these guide pages, you'll find example code for each.
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) -> {
return ctx.ack();
});
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 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) -> {
app.executorService().submit(() -> {
try {
ctx.client().viewsOpen(r -> r
.triggerId(ctx.getTriggerId())
.view(View.builder().build())
);
} catch (Exception e) {
}
});
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.
Are you already familiar with response_url
? If not, we recommend reading this guide 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) -> {
WebhookResponse result = ctx.respond(res -> res
.responseType("ephemeral")
.text("Hi there!")
);
return ctx.ack();
});
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) -> {
ChatPostMessageResponse response = ctx.client().chatPostMessage(r -> r
.channel(ctx.getChannelId())
.text(":wave: How are you?")
);
return ctx.ack();
});
For 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();
if (userToken != null) {
SearchMessagesResponse response = ctx.client().searchMessages(r -> r
.token(userToken)
.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
You can access SLF4J 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 library as the implementation of the APIs, you can configure the settings by logback.xml etc.
<configuration>
<appender name="default" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date %level [%thread] %logger{64} %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="default"/>
</root>
</configuration>
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;
String text;
}
String debugMode = System.getenv("SLACK_APP_DEBUG_MODE");
App app = new App();
if (debugMode != null && debugMode.equals("1")) {
app.use((req, _resp, chain) -> {
Response resp = chain.next(req);
if (resp.getStatusCode() != 200) {
resp.getHeaders().put("content-type", Arrays.asList(resp.getContentType()));
String headers = resp.getHeaders().entrySet().stream()
.map(e -> e.getKey() + ": " + e.getValue() + "\n").collect(joining());
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.
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.
Bolt turns the following middleware on by default:
ssl_check=1
requests from SlackAlthough 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);
appConfig.setSslCheckEnabled(false);
appConfig.setRequestVerificationEnabled(false);
App 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.
Refer to the supported web frameworks page for more details.
DeploymentsRetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4