Introduction
PagerDuty is a critical incident management platform that helps teams respond to system failures and operational issues quickly. Integrating PagerDuty with your Java applications allows for automated incident creation, escalation, and resolution.
This guide covers multiple approaches to trigger PagerDuty incidents from Java applications, from basic REST API integration to advanced Spring Boot starters and custom frameworks.
Architecture Overview
[Java Application] → [PagerDuty Client] → [PagerDuty API] → [Incident Management] ↓ ↓ ↓ ↓ Exception HTTP/REST Events API On-call Schedules Monitoring Wrapper Webhooks Escalation Policies Health Checks Async Processing Alerts Notifications
Step 1: Maven Dependencies
<properties>
<pagerduty.version>2.1.0</pagerduty.version>
<spring-boot.version>3.2.0</spring-boot.version>
</properties>
<dependencies>
<!-- HTTP Client for PagerDuty API -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
<!-- Resilience4j for circuit breaker -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>2.1.0</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Step 2: Configuration Properties
application.yml
pagerduty:
enabled: true
api:
base-url: https://api.pagerduty.com
events-url: https://events.pagerduty.com/v2/enqueue
routing-key: ${PAGERDUTY_ROUTING_KEY:your-routing-key-here}
integration-key: ${PAGERDUTY_INTEGRATION_KEY:}
incidents:
default-severity: error
default-component: backend-service
environment: ${ENVIRONMENT:production}
retry:
max-attempts: 3
backoff-delay: 1000
# Resilience4j Circuit Breaker for PagerDuty API
resilience4j:
circuitbreaker:
instances:
pagerdutyClient:
register-health-indicator: true
sliding-window-size: 10
minimum-number-of-calls: 5
permitted-number-of-calls-in-half-open-state: 3
automatic-transition-from-open-to-half-open-enabled: true
wait-duration-in-open-state: 10s
failure-rate-threshold: 50
event-consumer-buffer-size: 50
# Logging configuration
logging:
level:
com.example.pagerduty: DEBUG
Step 3: Data Models for PagerDuty API
Incident Trigger Models
PagerDutyEvent.java
package com.example.pagerduty.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PagerDutyEvent {
private final String routingKey;
private final String eventAction;
private final String dedupKey;
private final Payload payload;
private final Map<String, String> links;
private final Map<String, String> images;
public PagerDutyEvent(String routingKey, String eventAction,
String dedupKey, Payload payload) {
this.routingKey = routingKey;
this.eventAction = eventAction;
this.dedupKey = dedupKey;
this.payload = payload;
}
// Getters
@JsonProperty("routing_key")
public String getRoutingKey() { return routingKey; }
@JsonProperty("event_action")
public String getEventAction() { return eventAction; }
@JsonProperty("dedup_key")
public String getDedupKey() { return dedupKey; }
@JsonProperty("payload")
public Payload getPayload() { return payload; }
@JsonProperty("links")
public Map<String, String> getLinks() { return links; }
@JsonProperty("images")
public Map<String, String> getImages() { return images; }
// Builder pattern for fluent creation
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String routingKey;
private String eventAction;
private String dedupKey;
private Payload payload;
private Map<String, String> links;
private Map<String, String> images;
public Builder routingKey(String routingKey) {
this.routingKey = routingKey;
return this;
}
public Builder eventAction(String eventAction) {
this.eventAction = eventAction;
return this;
}
public Builder dedupKey(String dedupKey) {
this.dedupKey = dedupKey;
return this;
}
public Builder payload(Payload payload) {
this.payload = payload;
return this;
}
public Builder links(Map<String, String> links) {
this.links = links;
return this;
}
public Builder images(Map<String, String> images) {
this.images = images;
return this;
}
public PagerDutyEvent build() {
PagerDutyEvent event = new PagerDutyEvent(routingKey, eventAction, dedupKey, payload);
event.links = this.links;
event.images = this.images;
return event;
}
}
}
Payload.java
package com.example.pagerduty.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;
public class Payload {
private final String summary;
private final String source;
private final String severity;
private final long timestamp;
private final String component;
private final String group;
private final String classType;
private final Map<String, Object> customDetails;
public Payload(String summary, String source, String severity,
String component, Map<String, Object> customDetails) {
this.summary = summary;
this.source = source;
this.severity = severity;
this.timestamp = System.currentTimeMillis() / 1000; // Unix timestamp
this.component = component;
this.customDetails = customDetails;
}
// Getters
@JsonProperty("summary")
public String getSummary() { return summary; }
@JsonProperty("source")
public String getSource() { return source; }
@JsonProperty("severity")
public String getSeverity() { return severity; }
@JsonProperty("timestamp")
public long getTimestamp() { return timestamp; }
@JsonProperty("component")
public String getComponent() { return component; }
@JsonProperty("group")
public String getGroup() { return group; }
@JsonProperty("class")
public String getClassType() { return classType; }
@JsonProperty("custom_details")
public Map<String, Object> getCustomDetails() { return customDetails; }
public static class Builder {
private String summary;
private String source;
private String severity = "error";
private String component;
private String group;
private String classType;
private Map<String, Object> customDetails;
public Builder summary(String summary) {
this.summary = summary;
return this;
}
public Builder source(String source) {
this.source = source;
return this;
}
public Builder severity(String severity) {
this.severity = severity;
return this;
}
public Builder component(String component) {
this.component = component;
return this;
}
public Builder group(String group) {
this.group = group;
return this;
}
public Builder classType(String classType) {
this.classType = classType;
return this;
}
public Builder customDetails(Map<String, Object> customDetails) {
this.customDetails = customDetails;
return this;
}
public Payload build() {
Payload payload = new Payload(summary, source, severity, component, customDetails);
payload.group = this.group;
payload.classType = this.classType;
return payload;
}
}
}
PagerDutyResponse.java
package com.example.pagerduty.model;
import com.fasterxml.jackson.annotation.JsonProperty;
public class PagerDutyResponse {
private String status;
private String message;
private String dedupKey;
public PagerDutyResponse(@JsonProperty("status") String status,
@JsonProperty("message") String message,
@JsonProperty("dedup_key") String dedupKey) {
this.status = status;
this.message = message;
this.dedupKey = dedupKey;
}
// Getters and Setters
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
@JsonProperty("dedup_key")
public String getDedupKey() { return dedupKey; }
public void setDedupKey(String dedupKey) { this.dedupKey = dedupKey; }
public boolean isSuccess() {
return "success".equals(status);
}
}
Step 4: PagerDuty Client Implementation
Core PagerDuty Client
PagerDutyClient.java
package com.example.pagerduty.client;
import com.example.pagerduty.model.PagerDutyEvent;
import com.example.pagerduty.model.PagerDutyResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.Collections;
@Component
public class PagerDutyClient {
private static final Logger logger = LoggerFactory.getLogger(PagerDutyClient.class);
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
private final String eventsUrl;
private final String routingKey;
public PagerDutyClient(RestTemplate restTemplate,
ObjectMapper objectMapper,
@Value("${pagerduty.api.events-url}") String eventsUrl,
@Value("${pagerduty.api.routing-key}") String routingKey) {
this.restTemplate = restTemplate;
this.objectMapper = objectMapper;
this.eventsUrl = eventsUrl;
this.routingKey = routingKey;
}
public PagerDutyResponse triggerIncident(PagerDutyEvent event) {
try {
// Set routing key if not provided in event
if (event.getRoutingKey() == null) {
event = PagerDutyEvent.builder()
.routingKey(routingKey)
.eventAction(event.getEventAction())
.dedupKey(event.getDedupKey())
.payload(event.getPayload())
.build();
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity<PagerDutyEvent> request = new HttpEntity<>(event, headers);
logger.info("Sending PagerDuty incident: {}", event.getPayload().getSummary());
ResponseEntity<PagerDutyResponse> response = restTemplate.postForEntity(
eventsUrl, request, PagerDutyResponse.class);
if (response.getStatusCode() == HttpStatus.OK ||
response.getStatusCode() == HttpStatus.ACCEPTED) {
logger.info("PagerDuty incident triggered successfully: {}",
response.getBody().getDedupKey());
return response.getBody();
} else {
logger.error("Failed to trigger PagerDuty incident. Status: {}",
response.getStatusCode());
throw new RuntimeException("Failed to trigger PagerDuty incident");
}
} catch (Exception e) {
logger.error("Error triggering PagerDuty incident", e);
throw new RuntimeException("Failed to trigger PagerDuty incident", e);
}
}
public String triggerIncident(String summary, String source, String severity,
Map<String, Object> customDetails) {
PagerDutyEvent event = createEvent("trigger", summary, source, severity, customDetails);
PagerDutyResponse response = triggerIncident(event);
return response.getDedupKey();
}
public void resolveIncident(String dedupKey, String summary,
Map<String, Object> customDetails) {
PagerDutyEvent event = createEvent("resolve", summary,
"backend-service", "info", customDetails);
event = PagerDutyEvent.builder()
.routingKey(routingKey)
.eventAction("resolve")
.dedupKey(dedupKey)
.payload(event.getPayload())
.build();
triggerIncident(event);
}
public void acknowledgeIncident(String dedupKey, String summary) {
PagerDutyEvent event = createEvent("acknowledge", summary,
"backend-service", "info", null);
event = PagerDutyEvent.builder()
.routingKey(routingKey)
.eventAction("acknowledge")
.dedupKey(dedupKey)
.payload(event.getPayload())
.build();
triggerIncident(event);
}
private PagerDutyEvent createEvent(String action, String summary, String source,
String severity, Map<String, Object> customDetails) {
Payload payload = new Payload.Builder()
.summary(summary)
.source(source)
.severity(severity)
.component("java-backend")
.customDetails(customDetails)
.build();
return PagerDutyEvent.builder()
.eventAction(action)
.payload(payload)
.build();
}
}
Resilient PagerDuty Client with Circuit Breaker
ResilientPagerDutyService.java
package com.example.pagerduty.service;
import com.example.pagerduty.client.PagerDutyClient;
import com.example.pagerduty.model.PagerDutyEvent;
import com.example.pagerduty.model.PagerDutyResponse;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class ResilientPagerDutyService {
private static final Logger logger = LoggerFactory.getLogger(ResilientPagerDutyService.class);
private final PagerDutyClient pagerDutyClient;
private final Map<String, String> incidentCache; // Track incident dedup keys
public ResilientPagerDutyService(PagerDutyClient pagerDutyClient) {
this.pagerDutyClient = pagerDutyClient;
this.incidentCache = new ConcurrentHashMap<>();
}
@CircuitBreaker(name = "pagerdutyClient", fallbackMethod = "fallbackTrigger")
@Retry(name = "pagerdutyClient")
public String triggerIncident(String summary, String source, String severity,
Map<String, Object> customDetails) {
String dedupKey = pagerDutyClient.triggerIncident(summary, source, severity, customDetails);
incidentCache.put(generateIncidentKey(summary, source), dedupKey);
return dedupKey;
}
@CircuitBreaker(name = "pagerdutyClient")
public void resolveIncident(String incidentKey) {
String dedupKey = incidentCache.get(incidentKey);
if (dedupKey != null) {
pagerDutyClient.resolveIncident(dedupKey, "Incident resolved", null);
incidentCache.remove(incidentKey);
} else {
logger.warn("No incident found with key: {}", incidentKey);
}
}
// Fallback method when PagerDuty API is unavailable
private String fallbackTrigger(String summary, String source, String severity,
Map<String, Object> customDetails, Exception e) {
logger.error("PagerDuty service unavailable. Incident NOT triggered: {}", summary, e);
// You could queue these for retry, log to file, or send to alternative alerting
return "queued-" + System.currentTimeMillis();
}
private String generateIncidentKey(String summary, String source) {
return source + ":" + summary.hashCode();
}
public Map<String, String> getActiveIncidents() {
return Map.copyOf(incidentCache);
}
}
Step 5: Spring Configuration
PagerDutyConfig.java
package com.example.pagerduty.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
@Configuration
public class PagerDutyConfig {
@Bean
public RestTemplate pagerDutyRestTemplate() {
return new RestTemplate();
}
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
Step 6: Usage Examples
1. Basic Incident Triggering
OrderService.java
package com.example.service;
import com.example.pagerduty.service.ResilientPagerDutyService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
private final ResilientPagerDutyService pagerDutyService;
public OrderService(ResilientPagerDutyService pagerDutyService) {
this.pagerDutyService = pagerDutyService;
}
public void processOrder(OrderRequest request) {
try {
// Business logic
validateOrder(request);
processPayment(request);
updateInventory(request);
} catch (PaymentProcessingException e) {
// Trigger PagerDuty incident for payment failures
Map<String, Object> customDetails = new HashMap<>();
customDetails.put("order_id", request.getOrderId());
customDetails.put("amount", request.getAmount());
customDetails.put("payment_gateway", "stripe");
customDetails.put("error_code", e.getErrorCode());
customDetails.put("stack_trace", getStackTrace(e));
String incidentKey = pagerDutyService.triggerIncident(
"Payment processing failed for order " + request.getOrderId(),
"payment-service",
"error",
customDetails
);
logger.info("PagerDuty incident created: {}", incidentKey);
throw e;
} catch (InventoryException e) {
// Trigger incident for inventory issues
Map<String, Object> customDetails = new HashMap<>();
customDetails.put("order_id", request.getOrderId());
customDetails.put("product_id", request.getProductId());
customDetails.put("requested_quantity", request.getQuantity());
customDetails.put("available_stock", e.getAvailableStock());
pagerDutyService.triggerIncident(
"Inventory shortage for product " + request.getProductId(),
"inventory-service",
"warning",
customDetails
);
throw e;
}
}
private String getStackTrace(Exception e) {
StringBuilder sb = new StringBuilder();
for (StackTraceElement element : e.getStackTrace()) {
sb.append(element.toString()).append("\n");
}
return sb.toString();
}
}
2. Global Exception Handler
GlobalExceptionHandler.java
package com.example.pagerduty.handler;
import com.example.pagerduty.service.ResilientPagerDutyService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
private final ResilientPagerDutyService pagerDutyService;
public GlobalExceptionHandler(ResilientPagerDutyService pagerDutyService) {
this.pagerDutyService = pagerDutyService;
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<Map<String, String>> handleRuntimeException(RuntimeException e) {
logger.error("Unhandled runtime exception", e);
// Only trigger PagerDuty for critical exceptions
if (isCriticalException(e)) {
Map<String, Object> customDetails = new HashMap<>();
customDetails.put("exception_type", e.getClass().getSimpleName());
customDetails.put("exception_message", e.getMessage());
customDetails.put("stack_trace", getStackTrace(e));
pagerDutyService.triggerIncident(
"Critical runtime exception: " + e.getMessage(),
"backend-api",
"error",
customDetails
);
}
Map<String, String> response = new HashMap<>();
response.put("error", "Internal server error");
response.put("message", "An unexpected error occurred");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
private boolean isCriticalException(Exception e) {
// Define what constitutes a critical exception for your application
return e instanceof NullPointerException ||
e instanceof IllegalStateException ||
e.getMessage() != null && e.getMessage().contains("database");
}
private String getStackTrace(Exception e) {
StringBuilder sb = new StringBuilder();
sb.append(e.toString()).append("\n");
for (StackTraceElement element : e.getStackTrace()) {
sb.append("\tat ").append(element.toString()).append("\n");
}
return sb.toString();
}
}
3. Health Check with PagerDuty Integration
DatabaseHealthIndicator.java
package com.example.pagerduty.health;
import com.example.pagerduty.service.ResilientPagerDutyService;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.HashMap;
import java.util.Map;
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
private final ResilientPagerDutyService pagerDutyService;
private boolean incidentTriggered = false;
private String incidentKey;
public DatabaseHealthIndicator(ResilientPagerDutyService pagerDutyService) {
this.pagerDutyService = pagerDutyService;
}
@Override
public Health health() {
try {
// Test database connection
try (Connection conn = DriverManager.getConnection(
"jdbc:postgresql://localhost:5432/mydb", "user", "password")) {
if (conn.isValid(2)) {
// Database is healthy, resolve any previous incident
if (incidentTriggered) {
pagerDutyService.resolveIncident(incidentKey);
incidentTriggered = false;
}
return Health.up().withDetail("database", "Connected").build();
}
}
} catch (Exception e) {
// Database is down, trigger incident if not already triggered
if (!incidentTriggered) {
Map<String, Object> customDetails = new HashMap<>();
customDetails.put("error", e.getMessage());
customDetails.put("database_url", "jdbc:postgresql://localhost:5432/mydb");
incidentKey = pagerDutyService.triggerIncident(
"Database connection failure",
"database",
"critical",
customDetails
);
incidentTriggered = true;
}
return Health.down(e)
.withDetail("database", "Connection failed")
.withDetail("error", e.getMessage())
.build();
}
return Health.unknown().build();
}
}
Step 7: Testing
PagerDutyClientTest.java
package com.example.pagerduty.test;
import com.example.pagerduty.client.PagerDutyClient;
import com.example.pagerduty.model.PagerDutyEvent;
import com.example.pagerduty.model.PagerDutyResponse;
import com.example.pagerduty.model.Payload;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class PagerDutyClientTest {
@Mock
private RestTemplate restTemplate;
@InjectMocks
private PagerDutyClient pagerDutyClient;
@Test
void testTriggerIncident_Success() {
// Given
PagerDutyEvent event = createTestEvent();
PagerDutyResponse mockResponse = new PagerDutyResponse("success", "Event processed", "incident-123");
when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(PagerDutyResponse.class)))
.thenReturn(new ResponseEntity<>(mockResponse, HttpStatus.ACCEPTED));
// When
PagerDutyResponse response = pagerDutyClient.triggerIncident(event);
// Then
assertNotNull(response);
assertTrue(response.isSuccess());
assertEquals("incident-123", response.getDedupKey());
}
@Test
void testTriggerIncidentWithDetails() {
// Given
Map<String, Object> customDetails = new HashMap<>();
customDetails.put("service", "order-service");
customDetails.put("error_rate", "95%");
customDetails.put("duration_minutes", 15);
when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(PagerDutyResponse.class)))
.thenReturn(new ResponseEntity<>(
new PagerDutyResponse("success", "Event processed", "incident-456"),
HttpStatus.ACCEPTED
));
// When
String dedupKey = pagerDutyClient.triggerIncident(
"High error rate detected",
"order-service",
"error",
customDetails
);
// Then
assertNotNull(dedupKey);
assertEquals("incident-456", dedupKey);
}
private PagerDutyEvent createTestEvent() {
Payload payload = new Payload.Builder()
.summary("Test incident")
.source("test-service")
.severity("error")
.component("test-component")
.build();
return PagerDutyEvent.builder()
.routingKey("test-routing-key")
.eventAction("trigger")
.payload(payload)
.build();
}
}
Step 8: Advanced Features
1. Incident Management Service
IncidentManagementService.java
package com.example.pagerduty.service;
import org.springframework.stereotype.Service;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Service
public class IncidentManagementService {
private final ResilientPagerDutyService pagerDutyService;
private final ScheduledExecutorService scheduler;
private final ConcurrentHashMap<String, IncidentContext> activeIncidents;
public IncidentManagementService(ResilientPagerDutyService pagerDutyService) {
this.pagerDutyService = pagerDutyService;
this.scheduler = Executors.newScheduledThreadPool(2);
this.activeIncidents = new ConcurrentHashMap<>();
}
public String createIncident(String summary, String source, String severity,
Map<String, Object> details, long autoResolveMinutes) {
String incidentKey = pagerDutyService.triggerIncident(summary, source, severity, details);
// Schedule auto-resolution if specified
if (autoResolveMinutes > 0) {
scheduler.schedule(() -> {
if (activeIncidents.containsKey(incidentKey)) {
resolveIncident(incidentKey, "Auto-resolved after timeout");
}
}, autoResolveMinutes, TimeUnit.MINUTES);
}
activeIncidents.put(incidentKey, new IncidentContext(summary, source, System.currentTimeMillis()));
return incidentKey;
}
public void resolveIncident(String incidentKey, String resolutionNote) {
Map<String, Object> details = new HashMap<>();
details.put("resolution_note", resolutionNote);
details.put("duration_minutes",
(System.currentTimeMillis() - activeIncidents.get(incidentKey).getCreatedAt()) / 60000);
pagerDutyService.resolveIncident(incidentKey);
activeIncidents.remove(incidentKey);
}
public Map<String, IncidentContext> getActiveIncidents() {
return new HashMap<>(activeIncidents);
}
private static class IncidentContext {
private final String summary;
private final String source;
private final long createdAt;
public IncidentContext(String summary, String source, long createdAt) {
this.summary = summary;
this.source = source;
this.createdAt = createdAt;
}
// Getters
public String getSummary() { return summary; }
public String getSource() { return source; }
public long getCreatedAt() { return createdAt; }
}
}
Best Practices
- Use Appropriate Severity Levels:
critical: Service downtime, data losserror: Functional failures, high error rateswarning: Degraded performance, capacity issuesinfo: Informational alerts
- Include Relevant Context:
- Application version
- Environment (production/staging)
- Error codes and stack traces
- Business impact assessment
- Implement Deduplication:
- Use meaningful dedup keys to prevent duplicate incidents
- Group related errors under single incidents
- Circuit Breaker Pattern:
- Prevent cascading failures when PagerDuty is unavailable
- Implement fallback mechanisms for alert delivery
- Security:
- Store API keys securely (environment variables, vault)
- Use HTTPS for all API communications
- Implement proper authentication and authorization
Conclusion
This comprehensive PagerDuty integration provides:
- Robust incident triggering with proper error handling
- Resilient API communication with circuit breakers and retries
- Flexible incident management with acknowledgment and resolution
- Comprehensive monitoring integration with health checks
- Production-ready configuration with proper security practices
By implementing this solution, your Java applications can automatically trigger PagerDuty incidents for critical failures, ensuring your on-call teams are notified promptly and can respond quickly to production issues.