Slack Integration for Alerting and Notifications in Java

Introduction

Slack integration enables real-time alerting and notifications for applications, systems, and business processes. This comprehensive guide covers Slack integration patterns, message formatting, interactive components, and advanced alerting workflows in Java applications.


Core Concepts

1. Slack Integration Methods

  • Webhook Integration: Simple outgoing webhooks for message sending
  • Slack Apps: OAuth-based apps with rich interactive features
  • Socket Mode: Real-time bi-directional communication
  • Block Kit: Rich message formatting and interactive components

2. Alerting Patterns

  • System Alerts: Application errors, performance issues, system health
  • Business Alerts: Order processing, user activities, business metrics
  • Scheduled Reports: Daily summaries, weekly metrics, batch job results
  • Interactive Alerts: Buttons, menus, and actions for alert response

Project Setup and Dependencies

1. Maven Dependencies

<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Scheduling -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- Monitoring -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

2. Application Configuration

# application.yml
slack:
webhook:
url: ${SLACK_WEBHOOK_URL:}
enabled: true
bot:
token: ${SLACK_BOT_TOKEN:}
enabled: false
alerts:
channel: "#alerts"
environment: ${ENVIRONMENT:development}
application-name: alert-service
retry:
max-attempts: 3
backoff-ms: 1000
spring:
application:
name: slack-alerts
management:
endpoints:
web:
exposure:
include: health,metrics,info
endpoint:
health:
show-details: always
logging:
level:
com.example.slack: DEBUG

Core Slack Integration

1. Slack Configuration

@Configuration
@EnableConfigurationProperties(SlackProperties.class)
@Slf4j
public class SlackConfig {
@Bean
public WebClient webClient() {
return WebClient.builder()
.baseUrl("https://slack.com/api")
.defaultHeader("Content-Type", "application/json; charset=utf-8")
.build();
}
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.registerModule(new JavaTimeModule());
return mapper;
}
}
@ConfigurationProperties(prefix = "slack")
@Data
public class SlackProperties {
private Webhook webhook = new Webhook();
private Bot bot = new Bot();
private Alerts alerts = new Alerts();
@Data
public static class Webhook {
private String url;
private boolean enabled = false;
}
@Data
public static class Bot {
private String token;
private boolean enabled = false;
}
@Data
public static class Alerts {
private String channel = "#alerts";
private String environment = "development";
private String applicationName = "unknown";
private Retry retry = new Retry();
@Data
public static class Retry {
private int maxAttempts = 3;
private long backoffMs = 1000;
}
}
}

2. Slack Message Models

// Base message models
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SlackMessage {
private String channel;
private String text;
private List<Block> blocks;
private List<Attachment> attachments;
private String threadTs; // For threading
private String iconEmoji;
private String username;
public static SlackMessage simple(String text) {
return SlackMessage.builder()
.text(text)
.build();
}
public SlackMessage withChannel(String channel) {
this.channel = channel;
return this;
}
public SlackMessage withBlocks(Block... blocks) {
this.blocks = Arrays.asList(blocks);
return this;
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Block {
private String type;
private Text text;
private List<Element> elements;
private List<Field> fields;
private String blockId;
// Static factory methods
public static Block header(String text) {
return Block.builder()
.type("header")
.text(Text.plain(text))
.build();
}
public static Block section(Text text) {
return Block.builder()
.type("section")
.text(text)
.build();
}
public static Block sectionWithFields(Field... fields) {
return Block.builder()
.type("section")
.fields(Arrays.asList(fields))
.build();
}
public static Block divider() {
return Block.builder()
.type("divider")
.build();
}
public static Block actions(List<Element> elements) {
return Block.builder()
.type("actions")
.elements(elements)
.build();
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Text {
private String type;
private String text;
private boolean emoji;
public static Text plain(String text) {
return Text.builder()
.type("plain_text")
.text(text)
.emoji(true)
.build();
}
public static Text markdown(String text) {
return Text.builder()
.type("mrkdwn")
.text(text)
.build();
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Field {
private String type;
private String text;
public static Field of(String text) {
return Field.builder()
.type("mrkdwn")
.text(text)
.build();
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Element {
private String type;
private Text text;
private String actionId;
private String value;
private String url;
private String style; // primary, danger
public static Element button(String text, String actionId) {
return Element.builder()
.type("button")
.text(Text.plain(text))
.actionId(actionId)
.build();
}
public static Element button(String text, String actionId, String style) {
return Element.builder()
.type("button")
.text(Text.plain(text))
.actionId(actionId)
.style(style)
.build();
}
public static Element linkButton(String text, String url) {
return Element.builder()
.type("button")
.text(Text.plain(text))
.url(url)
.build();
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Attachment {
private String color; // good, warning, danger, or hex color
private String fallback;
private String title;
private String text;
private List<Field> fields;
private String footer;
private Long ts; // timestamp
public static Attachment success(String title, String text) {
return Attachment.builder()
.color("good")
.title(title)
.text(text)
.build();
}
public static Attachment warning(String title, String text) {
return Attachment.builder()
.color("warning")
.title(title)
.text(text)
.build();
}
public static Attachment danger(String title, String text) {
return Attachment.builder()
.color("danger")
.title(title)
.text(text)
.build();
}
public static Attachment custom(String color, String title, String text) {
return Attachment.builder()
.color(color)
.title(title)
.text(text)
.build();
}
}

3. Slack Client Service

@Service
@Slf4j
public class SlackClientService {
private final SlackProperties slackProperties;
private final WebClient webClient;
private final ObjectMapper objectMapper;
private final RetryTemplate retryTemplate;
public SlackClientService(SlackProperties slackProperties,
WebClient webClient,
ObjectMapper objectMapper) {
this.slackProperties = slackProperties;
this.webClient = webClient;
this.objectMapper = objectMapper;
this.retryTemplate = createRetryTemplate();
}
/**
* Send message via webhook
*/
public boolean sendWebhookMessage(SlackMessage message) {
if (!slackProperties.getWebhook().isEnabled()) {
log.warn("Slack webhook is disabled");
return false;
}
String webhookUrl = slackProperties.getWebhook().getUrl();
if (webhookUrl == null || webhookUrl.trim().isEmpty()) {
throw new SlackConfigurationException("Slack webhook URL is not configured");
}
return retryTemplate.execute(context -> {
try {
String response = webClient.post()
.uri(webhookUrl)
.bodyValue(message)
.retrieve()
.bodyToMono(String.class)
.block();
log.debug("Slack webhook response: {}", response);
return true;
} catch (Exception e) {
log.error("Failed to send Slack webhook message (attempt {})", 
context.getRetryCount() + 1, e);
throw new SlackSendException("Failed to send Slack message via webhook", e);
}
});
}
/**
* Send message via Bot API
*/
public boolean sendBotMessage(SlackMessage message) {
if (!slackProperties.getBot().isEnabled()) {
log.warn("Slack bot is disabled");
return false;
}
String botToken = slackProperties.getBot().getToken();
if (botToken == null || botToken.trim().isEmpty()) {
throw new SlackConfigurationException("Slack bot token is not configured");
}
// Set default channel if not provided
if (message.getChannel() == null) {
message.setChannel(slackProperties.getAlerts().getChannel());
}
return retryTemplate.execute(context -> {
try {
SlackApiResponse response = webClient.post()
.uri("/chat.postMessage")
.header("Authorization", "Bearer " + botToken)
.bodyValue(message)
.retrieve()
.bodyToMono(SlackApiResponse.class)
.block();
if (response != null && response.isOk()) {
log.debug("Slack bot message sent successfully: {}", response.getTs());
return true;
} else {
String error = response != null ? response.getError() : "Unknown error";
log.error("Slack API error: {}", error);
throw new SlackApiException("Slack API error: " + error);
}
} catch (Exception e) {
log.error("Failed to send Slack bot message (attempt {})", 
context.getRetryCount() + 1, e);
throw new SlackSendException("Failed to send Slack message via bot API", e);
}
});
}
/**
* Send message using configured method (webhook preferred)
*/
public boolean sendMessage(SlackMessage message) {
if (slackProperties.getWebhook().isEnabled()) {
return sendWebhookMessage(message);
} else if (slackProperties.getBot().isEnabled()) {
return sendBotMessage(message);
} else {
log.warn("No Slack integration method is enabled");
return false;
}
}
/**
* Update an existing message
*/
public boolean updateMessage(String channel, String timestamp, SlackMessage message) {
if (!slackProperties.getBot().isEnabled()) {
throw new SlackConfigurationException("Bot API required for message updates");
}
String botToken = slackProperties.getBot().getToken();
Map<String, Object> updateRequest = new HashMap<>();
updateRequest.put("channel", channel);
updateRequest.put("ts", timestamp);
updateRequest.put("text", message.getText());
updateRequest.put("blocks", message.getBlocks());
return retryTemplate.execute(context -> {
try {
SlackApiResponse response = webClient.post()
.uri("/chat.update")
.header("Authorization", "Bearer " + botToken)
.bodyValue(updateRequest)
.retrieve()
.bodyToMono(SlackApiResponse.class)
.block();
if (response != null && response.isOk()) {
log.debug("Slack message updated successfully: {}", response.getTs());
return true;
} else {
String error = response != null ? response.getError() : "Unknown error";
log.error("Slack API error on update: {}", error);
throw new SlackApiException("Slack API update error: " + error);
}
} catch (Exception e) {
log.error("Failed to update Slack message (attempt {})", 
context.getRetryCount() + 1, e);
throw new SlackSendException("Failed to update Slack message", e);
}
});
}
/**
* Upload file to Slack
*/
public boolean uploadFile(String channel, String filename, String content, String comment) {
if (!slackProperties.getBot().isEnabled()) {
throw new SlackConfigurationException("Bot API required for file uploads");
}
String botToken = slackProperties.getBot().getToken();
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("channels", channel);
bodyBuilder.part("filename", filename);
bodyBuilder.part("content", content);
if (comment != null) {
bodyBuilder.part("initial_comment", comment);
}
return retryTemplate.execute(context -> {
try {
SlackApiResponse response = webClient.post()
.uri("/files.upload")
.header("Authorization", "Bearer " + botToken)
.body(BodyInserters.fromMultipartData(bodyBuilder.build()))
.retrieve()
.bodyToMono(SlackApiResponse.class)
.block();
if (response != null && response.isOk()) {
log.debug("File uploaded to Slack successfully: {}", filename);
return true;
} else {
String error = response != null ? response.getError() : "Unknown error";
log.error("Slack file upload error: {}", error);
throw new SlackApiException("Slack file upload error: " + error);
}
} catch (Exception e) {
log.error("Failed to upload file to Slack (attempt {})", 
context.getRetryCount() + 1, e);
throw new SlackSendException("Failed to upload file to Slack", e);
}
});
}
private RetryTemplate createRetryTemplate() {
return RetryTemplate.builder()
.maxAttempts(slackProperties.getAlerts().getRetry().getMaxAttempts())
.exponentialBackoff(
slackProperties.getAlerts().getRetry().getBackoffMs(),
2,
slackProperties.getAlerts().getRetry().getBackoffMs() * 10
)
.retryOn(SlackSendException.class)
.build();
}
@Data
public static class SlackApiResponse {
private boolean ok;
private String error;
private String ts; // timestamp
private String channel;
private Message message;
@Data
public static class Message {
private String text;
private String username;
private String type;
private String subtype;
private String ts;
}
}
}

Alert Message Builders

1. Alert Message Factory

@Component
@Slf4j
public class SlackAlertFactory {
private final SlackProperties slackProperties;
public SlackAlertFactory(SlackProperties slackProperties) {
this.slackProperties = slackProperties;
}
/**
* Create system alert message
*/
public SlackMessage createSystemAlert(SystemAlert alert) {
String environment = slackProperties.getAlerts().getEnvironment();
String appName = slackProperties.getAlerts().getApplicationName();
return SlackMessage.builder()
.channel(slackProperties.getAlerts().getChannel())
.blocks(Arrays.asList(
Block.header("🚨 System Alert - " + alert.getSeverity()),
Block.divider(),
Block.section(Text.markdown(
"*Environment:* " + environment + "\n" +
"*Application:* " + appName + "\n" +
"*Service:* " + alert.getService() + "\n" +
"*Timestamp:* " + formatTimestamp(alert.getTimestamp())
)),
Block.section(Text.markdown("*Message:*\n" + alert.getMessage())),
createContextSection(alert.getContext()),
createActionsSection(alert)
))
.build();
}
/**
* Create business alert message
*/
public SlackMessage createBusinessAlert(BusinessAlert alert) {
return SlackMessage.builder()
.channel(slackProperties.getAlerts().getChannel())
.blocks(Arrays.asList(
Block.header("📊 Business Alert - " + alert.getType()),
Block.divider(),
Block.section(Text.markdown(
"*Business Process:* " + alert.getProcess() + "\n" +
"*Entity:* " + alert.getEntity() + "\n" +
"*Timestamp:* " + formatTimestamp(alert.getTimestamp())
)),
Block.section(Text.markdown("*Description:*\n" + alert.getDescription())),
Block.sectionWithFields(
Field.of("*Metric:*\n" + alert.getMetric()),
Field.of("*Value:*\n" + alert.getValue()),
Field.of("*Threshold:*\n" + alert.getThreshold())
),
createBusinessActionsSection(alert)
))
.build();
}
/**
* Create error alert message
*/
public SlackMessage createErrorAlert(ErrorAlert alert) {
String environment = slackProperties.getAlerts().getEnvironment();
List<Block> blocks = new ArrayList<>();
blocks.add(Block.header("❌ Error Alert"));
blocks.add(Block.divider());
blocks.add(Block.section(Text.markdown(
"*Environment:* " + environment + "\n" +
"*Application:* " + slackProperties.getAlerts().getApplicationName() + "\n" +
"*Timestamp:* " + formatTimestamp(alert.getTimestamp())
)));
blocks.add(Block.section(Text.markdown(
"*Error Type:* `" + alert.getErrorType() + "`\n" +
"*Message:* " + alert.getMessage()
)));
if (alert.getStackTrace() != null && !alert.getStackTrace().isEmpty()) {
blocks.add(Block.section(Text.markdown(
"*Stack Trace:*\n```" + abbreviateStackTrace(alert.getStackTrace()) + "```"
)));
}
blocks.add(createContextSection(alert.getContext()));
blocks.add(createErrorActionsSection(alert));
return SlackMessage.builder()
.channel(slackProperties.getAlerts().getChannel())
.blocks(blocks)
.build();
}
/**
* Create deployment notification
*/
public SlackMessage createDeploymentNotification(DeploymentNotification notification) {
String statusIcon = notification.isSuccess() ? "✅" : "❌";
return SlackMessage.builder()
.channel(slackProperties.getAlerts().getChannel())
.blocks(Arrays.asList(
Block.header(statusIcon + " Deployment " + 
(notification.isSuccess() ? "Successful" : "Failed")),
Block.divider(),
Block.section(Text.markdown(
"*Application:* " + notification.getApplication() + "\n" +
"*Environment:* " + notification.getEnvironment() + "\n" +
"*Version:* " + notification.getVersion() + "\n" +
"*Deployed By:* " + notification.getDeployedBy()
)),
Block.section(Text.markdown("*Commit Message:*\n" + notification.getCommitMessage())),
Block.section(Text.markdown(
"*Deployment Time:* " + formatTimestamp(notification.getDeploymentTime()) + "\n" +
"*Duration:* " + formatDuration(notification.getDuration())
)),
createDeploymentActionsSection(notification)
))
.build();
}
/**
* Create metric report
*/
public SlackMessage createMetricReport(MetricReport report) {
List<Block> blocks = new ArrayList<>();
blocks.add(Block.header("📈 Metric Report - " + report.getTimeRange()));
blocks.add(Block.divider());
// Summary section
blocks.add(Block.section(Text.markdown(
"*Period:* " + report.getTimeRange() + "\n" +
"*Generated At:* " + formatTimestamp(report.getGeneratedAt()) + "\n" +
"*Total Metrics:* " + report.getMetrics().size()
)));
// Metrics sections
for (MetricReport.Metric metric : report.getMetrics()) {
blocks.add(Block.sectionWithFields(
Field.of("*" + metric.getName() + "*\n" + metric.getValue()),
Field.of("*Trend:*\n" + getTrendIcon(metric.getTrend()) + " " + metric.getTrend()),
Field.of("*Status:*\n" + getStatusIcon(metric.getStatus()))
));
}
blocks.add(createReportActionsSection(report));
return SlackMessage.builder()
.channel(slackProperties.getAlerts().getChannel())
.blocks(blocks)
.build();
}
private Block createContextSection(Map<String, String> context) {
if (context == null || context.isEmpty()) {
return Block.section(Text.markdown("*Context:* No additional context"));
}
StringBuilder contextBuilder = new StringBuilder();
context.forEach((key, value) -> 
contextBuilder.append("• *").append(key).append(":* ").append(value).append("\n"));
return Block.section(Text.markdown("*Context:*\n" + contextBuilder));
}
private Block createActionsSection(SystemAlert alert) {
List<Element> elements = new ArrayList<>();
// View logs action
if (alert.getLogUrl() != null) {
elements.add(Element.linkButton("View Logs", alert.getLogUrl()));
}
// Acknowledge action
elements.add(Element.button("Acknowledge", "acknowledge_alert", "primary"));
// Escalate action for high severity
if (alert.getSeverity() == AlertSeverity.CRITICAL || alert.getSeverity() == AlertSeverity.HIGH) {
elements.add(Element.button("Escalate", "escalate_alert", "danger"));
}
return Block.actions(elements);
}
private Block createBusinessActionsSection(BusinessAlert alert) {
List<Element> elements = new ArrayList<>();
if (alert.getDashboardUrl() != null) {
elements.add(Element.linkButton("View Dashboard", alert.getDashboardUrl()));
}
if (alert.getEntityUrl() != null) {
elements.add(Element.linkButton("View Entity", alert.getEntityUrl()));
}
elements.add(Element.button("Acknowledge", "acknowledge_business_alert", "primary"));
return Block.actions(elements);
}
private Block createErrorActionsSection(ErrorAlert alert) {
List<Element> elements = new ArrayList<>();
if (alert.getLogsUrl() != null) {
elements.add(Element.linkButton("View Logs", alert.getLogsUrl()));
}
if (alert.getDashboardUrl() != null) {
elements.add(Element.linkButton("View Metrics", alert.getDashboardUrl()));
}
elements.add(Element.button("Acknowledge", "acknowledge_error", "primary"));
elements.add(Element.button("Create Ticket", "create_ticket", "danger"));
return Block.actions(elements);
}
private Block createDeploymentActionsSection(DeploymentNotification notification) {
List<Element> elements = new ArrayList<>();
if (notification.getBuildUrl() != null) {
elements.add(Element.linkButton("View Build", notification.getBuildUrl()));
}
if (notification.getLogsUrl() != null) {
elements.add(Element.linkButton("View Logs", notification.getLogsUrl()));
}
if (notification.getRollbackUrl() != null && !notification.isSuccess()) {
elements.add(Element.button("Rollback", "rollback_deployment", "danger"));
}
return Block.actions(elements);
}
private Block createReportActionsSection(MetricReport report) {
List<Element> elements = new ArrayList<>();
if (report.getDetailedReportUrl() != null) {
elements.add(Element.linkButton("View Detailed Report", report.getDetailedReportUrl()));
}
elements.add(Element.button("Schedule Meeting", "schedule_review", "primary"));
return Block.actions(elements);
}
private String formatTimestamp(Instant timestamp) {
return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z")
.format(timestamp.atZone(ZoneId.systemDefault()));
}
private String formatDuration(Duration duration) {
return duration.toMinutes() + " minutes";
}
private String abbreviateStackTrace(String stackTrace) {
if (stackTrace.length() <= 1000) {
return stackTrace;
}
return stackTrace.substring(0, 1000) + "\n... (truncated)";
}
private String getTrendIcon(String trend) {
return switch (trend.toLowerCase()) {
case "up" -> "📈";
case "down" -> "📉";
case "stable" -> "➡️";
default -> "🔸";
};
}
private String getStatusIcon(String status) {
return switch (status.toLowerCase()) {
case "healthy" -> "✅";
case "warning" -> "⚠️";
case "critical" -> "🔴";
default -> "⚪";
};
}
}

2. Alert Domain Models

// Alert domain models
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SystemAlert {
private String id;
private AlertSeverity severity;
private String service;
private String message;
private Instant timestamp;
private Map<String, String> context;
private String logUrl;
private String dashboardUrl;
public static SystemAlert critical(String service, String message) {
return SystemAlert.builder()
.id(generateId())
.severity(AlertSeverity.CRITICAL)
.service(service)
.message(message)
.timestamp(Instant.now())
.context(new HashMap<>())
.build();
}
public SystemAlert withContext(String key, String value) {
if (this.context == null) {
this.context = new HashMap<>();
}
this.context.put(key, value);
return this;
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class BusinessAlert {
private String id;
private String type;
private String process;
private String entity;
private String description;
private String metric;
private String value;
private String threshold;
private Instant timestamp;
private String dashboardUrl;
private String entityUrl;
public static BusinessAlert thresholdExceeded(String process, String metric, 
String value, String threshold) {
return BusinessAlert.builder()
.id(generateId())
.type("THRESHOLD_EXCEEDED")
.process(process)
.metric(metric)
.value(value)
.threshold(threshold)
.timestamp(Instant.now())
.build();
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ErrorAlert {
private String id;
private String errorType;
private String message;
private String stackTrace;
private Instant timestamp;
private Map<String, String> context;
private String logsUrl;
private String dashboardUrl;
public static ErrorAlert fromException(Exception e) {
return ErrorAlert.builder()
.id(generateId())
.errorType(e.getClass().getSimpleName())
.message(e.getMessage())
.stackTrace(getStackTrace(e))
.timestamp(Instant.now())
.context(new HashMap<>())
.build();
}
public ErrorAlert withContext(String key, String value) {
if (this.context == null) {
this.context = new HashMap<>();
}
this.context.put(key, value);
return this;
}
private static String getStackTrace(Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DeploymentNotification {
private String id;
private String application;
private String environment;
private String version;
private String deployedBy;
private String commitMessage;
private Instant deploymentTime;
private Duration duration;
private boolean success;
private String buildUrl;
private String logsUrl;
private String rollbackUrl;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MetricReport {
private String id;
private String timeRange;
private Instant generatedAt;
private List<Metric> metrics;
private String detailedReportUrl;
@Data
@Builder
public static class Metric {
private String name;
private String value;
private String trend; // UP, DOWN, STABLE
private String status; // HEALTHY, WARNING, CRITICAL
}
}
public enum AlertSeverity {
LOW, MEDIUM, HIGH, CRITICAL
}
// Utility method
private static String generateId() {
return "alert-" + UUID.randomUUID().toString().substring(0, 8);
}

Alert Service Implementation

1. Core Alert Service

@Service
@Slf4j
public class SlackAlertService {
private final SlackClientService slackClient;
private final SlackAlertFactory alertFactory;
private final AlertMetrics metrics;
public SlackAlertService(SlackClientService slackClient,
SlackAlertFactory alertFactory,
AlertMetrics metrics) {
this.slackClient = slackClient;
this.alertFactory = alertFactory;
this.metrics = metrics;
}
/**
* Send system alert
*/
public AlertResult sendSystemAlert(SystemAlert alert) {
try {
SlackMessage message = alertFactory.createSystemAlert(alert);
boolean sent = slackClient.sendMessage(message);
AlertResult result = AlertResult.builder()
.alertId(alert.getId())
.success(sent)
.timestamp(Instant.now())
.build();
metrics.recordAlertSent("system", alert.getSeverity().name(), sent);
log.info("System alert sent: {} - {}", alert.getSeverity(), alert.getMessage());
return result;
} catch (Exception e) {
log.error("Failed to send system alert: {}", alert.getId(), e);
metrics.recordAlertError("system");
return AlertResult.failure(alert.getId(), e.getMessage());
}
}
/**
* Send business alert
*/
public AlertResult sendBusinessAlert(BusinessAlert alert) {
try {
SlackMessage message = alertFactory.createBusinessAlert(alert);
boolean sent = slackClient.sendMessage(message);
AlertResult result = AlertResult.builder()
.alertId(alert.getId())
.success(sent)
.timestamp(Instant.now())
.build();
metrics.recordAlertSent("business", alert.getType(), sent);
log.info("Business alert sent: {} - {}", alert.getType(), alert.getDescription());
return result;
} catch (Exception e) {
log.error("Failed to send business alert: {}", alert.getId(), e);
metrics.recordAlertError("business");
return AlertResult.failure(alert.getId(), e.getMessage());
}
}
/**
* Send error alert
*/
public AlertResult sendErrorAlert(ErrorAlert alert) {
try {
SlackMessage message = alertFactory.createErrorAlert(alert);
boolean sent = slackClient.sendMessage(message);
AlertResult result = AlertResult.builder()
.alertId(alert.getId())
.success(sent)
.timestamp(Instant.now())
.build();
metrics.recordAlertSent("error", "ERROR", sent);
log.info("Error alert sent: {} - {}", alert.getErrorType(), alert.getMessage());
return result;
} catch (Exception e) {
log.error("Failed to send error alert: {}", alert.getId(), e);
metrics.recordAlertError("error");
return AlertResult.failure(alert.getId(), e.getMessage());
}
}
/**
* Send deployment notification
*/
public AlertResult sendDeploymentNotification(DeploymentNotification notification) {
try {
SlackMessage message = alertFactory.createDeploymentNotification(notification);
boolean sent = slackClient.sendMessage(message);
AlertResult result = AlertResult.builder()
.alertId(notification.getId())
.success(sent)
.timestamp(Instant.now())
.build();
metrics.recordAlertSent("deployment", 
notification.isSuccess() ? "SUCCESS" : "FAILURE", sent);
log.info("Deployment notification sent: {} - {}", 
notification.getApplication(), notification.getVersion());
return result;
} catch (Exception e) {
log.error("Failed to send deployment notification: {}", notification.getId(), e);
metrics.recordAlertError("deployment");
return AlertResult.failure(notification.getId(), e.getMessage());
}
}
/**
* Send metric report
*/
public AlertResult sendMetricReport(MetricReport report) {
try {
SlackMessage message = alertFactory.createMetricReport(report);
boolean sent = slackClient.sendMessage(message);
AlertResult result = AlertResult.builder()
.alertId(report.getId())
.success(sent)
.timestamp(Instant.now())
.build();
metrics.recordAlertSent("metric_report", "REPORT", sent);
log.info("Metric report sent: {} - {}", report.getTimeRange(), report.getMetrics().size());
return result;
} catch (Exception e) {
log.error("Failed to send metric report: {}", report.getId(), e);
metrics.recordAlertError("metric_report");
return AlertResult.failure(report.getId(), e.getMessage());
}
}
/**
* Send batch alerts (for multiple alerts of same type)
*/
public List<AlertResult> sendBatchAlerts(List<SystemAlert> alerts) {
return alerts.stream()
.map(this::sendSystemAlert)
.collect(Collectors.toList());
}
/**
* Send alert with retry
*/
public AlertResult sendAlertWithRetry(SystemAlert alert, int maxRetries) {
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
AlertResult result = sendSystemAlert(alert);
if (result.isSuccess()) {
return result;
}
} catch (Exception e) {
log.warn("Alert send attempt {} failed for alert: {}", attempt, alert.getId(), e);
}
if (attempt < maxRetries) {
try {
Thread.sleep(1000 * attempt); // Exponential backoff
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
return AlertResult.failure(alert.getId(), "All retry attempts failed");
}
}
@Data
@Builder
public class AlertResult {
private String alertId;
private boolean success;
private String errorMessage;
private Instant timestamp;
public static AlertResult failure(String alertId, String errorMessage) {
return AlertResult.builder()
.alertId(alertId)
.success(false)
.errorMessage(errorMessage)
.timestamp(Instant.now())
.build();
}
}

2. Alert Metrics

@Component
@Slf4j
public class AlertMetrics {
private final MeterRegistry meterRegistry;
private final Counter alertSentCounter;
private final Counter alertErrorCounter;
private final Timer alertSendTimer;
public AlertMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.alertSentCounter = meterRegistry.counter("slack.alerts.sent");
this.alertErrorCounter = meterRegistry.counter("slack.alerts.errors");
this.alertSendTimer = meterRegistry.timer("slack.alerts.send.duration");
}
public void recordAlertSent(String alertType, String severity, boolean success) {
alertSentCounter.increment();
Tags tags = Tags.of(
Tag.of("alert_type", alertType),
Tag.of("severity", severity),
Tag.of("success", String.valueOf(success))
);
meterRegistry.counter("slack.alerts.sent.detailed", tags).increment();
log.debug("Recorded alert sent - type: {}, severity: {}, success: {}", 
alertType, severity, success);
}
public void recordAlertError(String alertType) {
alertErrorCounter.increment();
Tags tags = Tags.of(Tag.of("alert_type", alertType));
meterRegistry.counter("slack.alerts.errors.detailed", tags).increment();
log.warn("Recorded alert error - type: {}", alertType);
}
public Timer.Sample startTimer() {
return Timer.start(meterRegistry);
}
public void recordTimer(Timer.Sample sample, String alertType) {
sample.stop(Timer.builder("slack.alerts.send.duration.detailed")
.tag("alert_type", alertType)
.register(meterRegistry));
}
}

Interactive Alert Handling

1. Slack Interactive Component Handler

@Service
@Slf4j
public class SlackInteractiveHandler {
private final SlackClientService slackClient;
private final AlertAcknowledgmentService acknowledgmentService;
private final TicketService ticketService;
private final ObjectMapper objectMapper;
public SlackInteractiveHandler(SlackClientService slackClient,
AlertAcknowledgmentService acknowledgmentService,
TicketService ticketService,
ObjectMapper objectMapper) {
this.slackClient = slackClient;
this.acknowledgmentService = acknowledgmentService;
this.ticketService = ticketService;
this.objectMapper = objectMapper;
}
/**
* Handle interactive component payload from Slack
*/
public void handleInteractivePayload(String payloadJson) {
try {
JsonNode payload = objectMapper.readTree(payloadJson);
String type = payload.get("type").asText();
switch (type) {
case "block_actions":
handleBlockActions(payload);
break;
case "view_submission":
handleViewSubmission(payload);
break;
default:
log.warn("Unknown interactive payload type: {}", type);
}
} catch (Exception e) {
log.error("Failed to handle interactive payload", e);
}
}
/**
* Handle block actions (button clicks, menu selections)
*/
private void handleBlockActions(JsonNode payload) {
JsonNode actions = payload.get("actions");
if (actions == null || !actions.isArray()) {
return;
}
for (JsonNode action : actions) {
String actionId = action.get("action_id").asText();
JsonNode responseUrl = payload.get("response_url");
switch (actionId) {
case "acknowledge_alert":
handleAcknowledgeAlert(payload, action, responseUrl);
break;
case "escalate_alert":
handleEscalateAlert(payload, action, responseUrl);
break;
case "create_ticket":
handleCreateTicket(payload, action, responseUrl);
break;
case "rollback_deployment":
handleRollbackDeployment(payload, action, responseUrl);
break;
default:
log.warn("Unknown action ID: {}", actionId);
}
}
}
/**
* Handle alert acknowledgment
*/
private void handleAcknowledgeAlert(JsonNode payload, JsonNode action, JsonNode responseUrl) {
try {
String userId = payload.get("user").get("id").asText();
String userName = payload.get("user").get("username").asText();
String channelId = payload.get("channel").get("id").asText();
String messageTs = payload.get("message").get("ts").asText();
// Extract alert context from message
String alertId = extractAlertIdFromMessage(payload);
// Acknowledge the alert
acknowledgmentService.acknowledgeAlert(alertId, userId, userName);
// Update the message to show acknowledgment
updateMessageWithAcknowledgment(channelId, messageTs, userName);
log.info("Alert acknowledged by {}: {}", userName, alertId);
} catch (Exception e) {
log.error("Failed to handle alert acknowledgment", e);
sendEphemeralError(responseUrl.asText(), "Failed to acknowledge alert");
}
}
/**
* Handle alert escalation
*/
private void handleEscalateAlert(JsonNode payload, JsonNode action, JsonNode responseUrl) {
try {
String userId = payload.get("user").get("id").asText();
String userName = payload.get("user").get("username").asText();
String channelId = payload.get("channel").get("id").asText();
String messageTs = payload.get("message").get("ts").asText();
String alertId = extractAlertIdFromMessage(payload);
// Escalate the alert
acknowledgmentService.escalateAlert(alertId, userId, userName);
// Update message to show escalation
updateMessageWithEscalation(channelId, messageTs, userName);
log.info("Alert escalated by {}: {}", userName, alertId);
} catch (Exception e) {
log.error("Failed to handle alert escalation", e);
sendEphemeralError(responseUrl.asText(), "Failed to escalate alert");
}
}
/**
* Handle ticket creation
*/
private void handleCreateTicket(JsonNode payload, JsonNode action, JsonNode responseUrl) {
try {
String userId = payload.get("user").get("id").asText();
String userName = payload.get("user").get("username").asText();
String alertId = extractAlertIdFromMessage(payload);
// Create support ticket
String ticketId = ticketService.createTicketFromAlert(alertId, userId, userName);
// Send confirmation
sendEphemeralMessage(responseUrl.asText(), 
"✅ Ticket created: " + ticketId + "\nThe support team will investigate the issue.");
log.info("Ticket created by {}: {} for alert {}", userName, ticketId, alertId);
} catch (Exception e) {
log.error("Failed to create ticket", e);
sendEphemeralError(responseUrl.asText(), "Failed to create support ticket");
}
}
/**
* Handle deployment rollback
*/
private void handleRollbackDeployment(JsonNode payload, JsonNode action, JsonNode responseUrl) {
try {
String userId = payload.get("user").get("id").asText();
String userName = payload.get("user").get("username").asText();
// Extract deployment info from message
String deploymentId = extractDeploymentIdFromMessage(payload);
// Initiate rollback
// deploymentService.rollback(deploymentId, userId);
sendEphemeralMessage(responseUrl.asText(), 
"🔄 Rollback initiated for deployment: " + deploymentId + 
"\nThis may take a few minutes...");
log.info("Rollback initiated by {} for deployment: {}", userName, deploymentId);
} catch (Exception e) {
log.error("Failed to handle deployment rollback", e);
sendEphemeralError(responseUrl.asText(), "Failed to initiate rollback");
}
}
private void handleViewSubmission(JsonNode payload) {
// Handle modal form submissions
log.debug("View submission received: {}", payload);
}
private String extractAlertIdFromMessage(JsonNode payload) {
// Extract alert ID from message blocks or context
// This is a simplified implementation
JsonNode message = payload.get("message");
if (message != null) {
JsonNode blocks = message.get("blocks");
if (blocks != null && blocks.isArray()) {
// Look for alert ID in message blocks
for (JsonNode block : blocks) {
JsonNode text = block.get("text");
if (text != null) {
String textValue = text.get("text").asText();
if (textValue.contains("Alert ID:")) {
// Extract alert ID from text
return textValue.split("Alert ID:")[1].trim();
}
}
}
}
}
return "unknown";
}
private String extractDeploymentIdFromMessage(JsonNode payload) {
// Similar implementation to extract deployment ID
return "deploy-" + System.currentTimeMillis();
}
private void updateMessageWithAcknowledgment(String channelId, String messageTs, String userName) {
try {
// Create updated message with acknowledgment
SlackMessage updatedMessage = SlackMessage.builder()
.blocks(Arrays.asList(
Block.section(Text.markdown(
"✅ *Alert Acknowledged*\n" +
"Acknowledged by: @" + userName + "\n" +
"Time: " + Instant.now().atZone(ZoneId.systemDefault()).format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
))
))
.build();
slackClient.updateMessage(channelId, messageTs, updatedMessage);
} catch (Exception e) {
log.error("Failed to update message with acknowledgment", e);
}
}
private void updateMessageWithEscalation(String channelId, String messageTs, String userName) {
try {
SlackMessage updatedMessage = SlackMessage.builder()
.blocks(Arrays.asList(
Block.section(Text.markdown(
"🚨 *Alert Escalated*\n" +
"Escalated by: @" + userName + "\n" +
"Time: " + Instant.now().atZone(ZoneId.systemDefault()).format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "\n" +
"The on-call engineer has been notified."
))
))
.build();
slackClient.updateMessage(channelId, messageTs, updatedMessage);
} catch (Exception e) {
log.error("Failed to update message with escalation", e);
}
}
private void sendEphemeralMessage(String responseUrl, String message) {
try {
SlackMessage ephemeralMessage = SlackMessage.simple(message);
// Use response URL to send ephemeral message
// Implementation depends on your HTTP client setup
} catch (Exception e) {
log.error("Failed to send ephemeral message", e);
}
}
private void sendEphemeralError(String responseUrl, String error) {
sendEphemeralMessage(responseUrl, "❌ " + error);
}
}

2. Supporting Services

@Service
@Slf4j
public class AlertAcknowledgmentService {
public void acknowledgeAlert(String alertId, String userId, String userName) {
// Store acknowledgment in database
// Update alert status
// Notify other systems
log.info("Alert {} acknowledged by {} ({})", alertId, userName, userId);
}
public void escalateAlert(String alertId, String userId, String userName) {
// Escalate to on-call engineer
// Update alert priority
// Send additional notifications
log.info("Alert {} escalated by {} ({})", alertId, userName, userId);
}
}
@Service
@Slf4j
public class TicketService {
public String createTicketFromAlert(String alertId, String userId, String userName) {
// Create support ticket in ticketing system
// Link to alert
// Assign to appropriate team
String ticketId = "TKT-" + System.currentTimeMillis();
log.info("Ticket {} created for alert {} by {} ({})", 
ticketId, alertId, userName, userId);
return ticketId;
}
}

Scheduled Alerting

1. Scheduled Report Service

@Service
@Slf4j
public class ScheduledAlertService {
private final SlackAlertService alertService;
private final MetricService metricService;
public ScheduledAlertService(SlackAlertService alertService,
MetricService metricService) {
this.alertService = alertService;
this.metricService = metricService;
}
/**
* Send daily system health report
*/
@Scheduled(cron = "0 0 9 * * ?") // 9 AM daily
public void sendDailyHealthReport() {
try {
MetricReport report = metricService.generateDailyHealthReport();
alertService.sendMetricReport(report);
log.info("Daily health report sent successfully");
} catch (Exception e) {
log.error("Failed to send daily health report", e);
}
}
/**
* Send weekly performance report
*/
@Scheduled(cron = "0 0 10 * * MON") // 10 AM every Monday
public void sendWeeklyPerformanceReport() {
try {
MetricReport report = metricService.generateWeeklyPerformanceReport();
alertService.sendMetricReport(report);
log.info("Weekly performance report sent successfully");
} catch (Exception e) {
log.error("Failed to send weekly performance report", e);
}
}
/**
* Send system status check (every hour)
*/
@Scheduled(cron = "0 0 * * * ?") // Every hour
public void sendHourlyStatusCheck() {
try {
SystemStatus status = metricService.checkSystemStatus();
if (!status.isHealthy()) {
SystemAlert alert = SystemAlert.critical("System Status Check", 
"System health check failed: " + status.getMessage());
alertService.sendSystemAlert(alert);
}
} catch (Exception e) {
log.error("Failed to send hourly status check", e);
}
}
}
@Service
@Slf4j
public class MetricService {
public MetricReport generateDailyHealthReport() {
// Collect daily metrics
List<MetricReport.Metric> metrics = Arrays.asList(
MetricReport.Metric.builder()
.name("API Success Rate")
.value("99.8%")
.trend("STABLE")
.status("HEALTHY")
.build(),
MetricReport.Metric.builder()
.name("Response Time")
.value("245ms")
.trend("DOWN")
.status("HEALTHY")
.build(),
MetricReport.Metric.builder()
.name("Error Rate")
.value("0.2%")
.trend("UP")
.status("WARNING")
.build()
);
return MetricReport.builder()
.id("report-" + Instant.now().toEpochMilli())
.timeRange("Last 24 Hours")
.generatedAt(Instant.now())
.metrics(metrics)
.detailedReportUrl("https://metrics.example.com/daily")
.build();
}
public MetricReport generateWeeklyPerformanceReport() {
// Collect weekly metrics
List<MetricReport.Metric> metrics = Arrays.asList(
MetricReport.Metric.builder()
.name("Weekly Uptime")
.value("99.95%")
.trend("UP")
.status("HEALTHY")
.build(),
MetricReport.Metric.builder()
.name("Peak Load")
.value("1,250 RPS")
.trend("UP")
.status("HEALTHY")
.build(),
MetricReport.Metric.builder()
.name("Active Users")
.value("45,230")
.trend("UP")
.status("HEALTHY")
.build()
);
return MetricReport.builder()
.id("weekly-" + Instant.now().toEpochMilli())
.timeRange("Last Week")
.generatedAt(Instant.now())
.metrics(metrics)
.detailedReportUrl("https://metrics.example.com/weekly")
.build();
}
public SystemStatus checkSystemStatus() {
// Check various system components
// This is a simplified implementation
boolean isHealthy = checkDatabase() && checkCache() && checkExternalServices();
return SystemStatus.builder()
.healthy(isHealthy)
.message(isHealthy ? "All systems operational" : "Some systems are experiencing issues")
.timestamp(Instant.now())
.build();
}
private boolean checkDatabase() {
// Implement database health check
return true;
}
private boolean checkCache() {
// Implement cache health check
return true;
}
private boolean checkExternalServices() {
// Implement external services health check
return true;
}
}
@Data
@Builder
public class SystemStatus {
private boolean healthy;
private String message;
private Instant timestamp;
}

REST Controller

@RestController
@RequestMapping("/api/alerts")
@Slf4j
public class AlertController {
private final SlackAlertService alertService;
private final SlackInteractiveHandler interactiveHandler;
public AlertController(SlackAlertService alertService,
SlackInteractiveHandler interactiveHandler) {
this.alertService = alertService;
this.interactiveHandler = interactiveHandler;
}
@PostMapping("/system")
public ResponseEntity<AlertResult> sendSystemAlert(@RequestBody SystemAlertRequest request) {
try {
SystemAlert alert = SystemAlert.critical(request.getService(), request.getMessage());
// Add context from request
if (request.getContext() != null) {
request.getContext().forEach(alert::withContext);
}
AlertResult result = alertService.sendSystemAlert(alert);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("Failed to send system alert", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(AlertResult.failure("unknown", e.getMessage()));
}
}
@PostMapping("/error")
public ResponseEntity<AlertResult> sendErrorAlert(@RequestBody ErrorAlertRequest request) {
try {
ErrorAlert alert = ErrorAlert.fromException(request.getException());
alert.setContext(request.getContext());
AlertResult result = alertService.sendErrorAlert(alert);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("Failed to send error alert", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(AlertResult.failure("unknown", e.getMessage()));
}
}
@PostMapping("/interactive")
public ResponseEntity<Void> handleInteractive(@RequestBody String payload) {
try {
interactiveHandler.handleInteractivePayload(payload);
return ResponseEntity.ok().build();
} catch (Exception e) {
log.error("Failed to handle interactive payload", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PostMapping("/deployment")
public ResponseEntity<AlertResult> sendDeploymentNotification(@RequestBody DeploymentNotificationRequest request) {
try {
DeploymentNotification notification = DeploymentNotification.builder()
.id("deploy-" + System.currentTimeMillis())
.application(request.getApplication())
.environment(request.getEnvironment())
.version(request.getVersion())
.deployedBy(request.getDeployedBy())
.commitMessage(request.getCommitMessage())
.deploymentTime(Instant.now())
.duration(request.getDuration())
.success(request.isSuccess())
.build();
AlertResult result = alertService.sendDeploymentNotification(notification);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("Failed to send deployment notification", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(AlertResult.failure("unknown", e.getMessage()));
}
}
// Request DTOs
@Data
public static class SystemAlertRequest {
private String service;
private String message;
private Map<String, String> context;
}
@Data
public static class ErrorAlertRequest {
private Exception exception;
private Map<String, String> context;
}
@Data
public static class DeploymentNotificationRequest {
private String application;
private String environment;
private String version;
private String deployedBy;
private String commitMessage;
private Duration duration;
private boolean success;
}
}

Exception Handling

// Custom exceptions
public class SlackConfigurationException extends RuntimeException {
public SlackConfigurationException(String message) {
super(message);
}
public SlackConfigurationException(String message, Throwable cause) {
super(message, cause);
}
}
public class SlackSendException extends RuntimeException {
public SlackSendException(String message) {
super(message);
}
public SlackSendException(String message, Throwable cause) {
super(message, cause);
}
}
public class SlackApiException extends RuntimeException {
public SlackApiException(String message) {
super(message);
}
public SlackApiException(String message, Throwable cause) {
super(message, cause);
}
}
// Global exception handler
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
private final SlackAlertService alertService;
public GlobalExceptionHandler(SlackAlertService alertService) {
this.alertService = alertService;
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception e) {
log.error("Unhandled exception", e);
// Send alert for critical errors
if (isCriticalError(e)) {
ErrorAlert alert = ErrorAlert.fromException(e)
.withContext("endpoint", "unknown")
.withContext("timestamp", Instant.now().toString());
alertService.sendErrorAlert(alert);
}
ErrorResponse error = ErrorResponse.builder()
.error("Internal Server Error")
.message("An unexpected error occurred")
.timestamp(Instant.now())
.build();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
private boolean isCriticalError(Exception e) {
// Define what constitutes a critical error
return e instanceof NullPointerException || 
e instanceof IllegalArgumentException ||
e instanceof IllegalStateException;
}
@Data
@Builder
public static class ErrorResponse {
private String error;
private String message;
private Instant timestamp;
}
}

Testing

1. Test Configuration

@SpringBootTest
@ActiveProfiles("test")
class SlackAlertServiceTest {
@MockBean
private SlackClientService slackClient;
@Autowired
private SlackAlertService alertService;
@Test
void testSendSystemAlertSuccess() {
// Given
SystemAlert alert = SystemAlert.critical("test-service", "Test alert message");
when(slackClient.sendMessage(any())).thenReturn(true);
// When
AlertResult result = alertService.sendSystemAlert(alert);
// Then
assertTrue(result.isSuccess());
assertEquals(alert.getId(), result.getAlertId());
verify(slackClient).sendMessage(any(SlackMessage.class));
}
@Test
void testSendSystemAlertFailure() {
// Given
SystemAlert alert = SystemAlert.critical("test-service", "Test alert message");
when(slackClient.sendMessage(any())).thenThrow(new SlackSendException("Test exception"));
// When
AlertResult result = alertService.sendSystemAlert(alert);
// Then
assertFalse(result.isSuccess());
assertEquals(alert.getId(), result.getAlertId());
assertNotNull(result.getErrorMessage());
}
@Test
void testSendErrorAlert() {
// Given
Exception testException = new RuntimeException("Test exception");
ErrorAlert alert = ErrorAlert.fromException(testException);
when(slackClient.sendMessage(any())).thenReturn(true);
// When
AlertResult result = alertService.sendErrorAlert(alert);
// Then
assertTrue(result.isSuccess());
verify(slackClient).sendMessage(any(SlackMessage.class));
}
}

Conclusion

Slack integration for alerts provides powerful real-time notification capabilities for Java applications. The implementation covers:

Key Features:

  • Multiple Integration Methods: Webhooks and Bot API
  • Rich Message Formatting: Block Kit for structured messages
  • Interactive Components: Buttons and actions for alert response
  • Scheduled Reporting: Automated health checks and metrics
  • Comprehensive Alert Types: System, business, error, and deployment alerts

Best Practices:

  • Retry Mechanisms: Handle transient failures gracefully
  • Rate Limiting: Respect Slack API limits
  • Security: Secure webhook URLs and bot tokens
  • Monitoring: Track alert delivery and performance
  • Error Handling: Comprehensive exception handling and fallbacks

Use Cases:

  • System Monitoring: Application errors, performance issues, health checks
  • Business Alerts: Order processing, user activities, threshold breaches
  • Deployment Notifications: CI/CD pipeline status, rollback alerts
  • Scheduled Reports: Daily health summaries, weekly performance metrics
  • Interactive Operations: Alert acknowledgment, ticket creation, escalation

By implementing this comprehensive Slack alerting system, you can ensure timely notifications, enable quick response to issues, and maintain visibility into your application's health and performance.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper