PagerDuty is a critical incident management platform used by DevOps and SRE teams. This guide covers how to programmatically trigger, manage, and resolve PagerDuty incidents from Java applications.
Project Setup and Dependencies
1. Maven Dependencies
<dependencies> <!-- PagerDuty REST API Client --> <dependency> <groupId>com.pagerduty</groupId> <artifactId>pagerduty-client</artifactId> <version>1.0.0</version> </dependency> <!-- HTTP Client --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.7</version> </dependency> <!-- Spring Boot Starter Web (Optional) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.1.0</version> </dependency> <!-- Testing --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>5.3.1</version> <scope>test</scope> </dependency> <dependency> <groupId>com.github.tomakehurst</groupId> <artifactId>wiremock-jre8</artifactId> <version>2.35.0</version> <scope>test</scope> </dependency> </dependencies>
Core PagerDuty Integration
1. Configuration Classes
// PagerDutyConfig.java @Configuration @ConfigurationProperties(prefix = "pagerduty") @Data public class PagerDutyConfig { private String apiKey; private String integrationKey; private String apiUrl = "https://api.pagerduty.com"; private int timeoutSeconds = 30; private int maxRetries = 3; private String fromEmail; private String serviceId; public void validate() { if (apiKey == null || apiKey.trim().isEmpty()) { throw new IllegalStateException("PagerDuty API key is required"); } if (integrationKey == null || integrationKey.trim().isEmpty()) { throw new IllegalStateException("PagerDuty integration key is required"); } } } // IncidentSeverity.java public enum IncidentSeverity { CRITICAL("critical"), ERROR("error"), WARNING("warning"), INFO("info"); private final String value; IncidentSeverity(String value) { this.value = value; } public String getValue() { return value; } } // IncidentAction.java public enum IncidentAction { TRIGGER("trigger"), ACKNOWLEDGE("acknowledge"), RESOLVE("resolve"); private final String value; IncidentAction(String value) { this.value = value; } public String getValue() { return value; } } 2. Request/Response DTOs
// PagerDutyIncidentRequest.java @Data @Builder @AllArgsConstructor @NoArgsConstructor public class PagerDutyIncidentRequest { @JsonProperty("routing_key") private String routingKey; @JsonProperty("event_action") private String eventAction; @JsonProperty("dedup_key") private String dedupKey; private Payload payload; @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class Payload { private String summary; private String source; private String severity; private String timestamp; @JsonProperty("custom_details") private Map<String, Object> customDetails; @JsonProperty("component") private String component; @JsonProperty("group") private String group; @JsonProperty("class") private String eventClass; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class Image { private String src; private String href; private String alt; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class Link { private String href; private String text; } } // PagerDutyIncidentResponse.java @Data public class PagerDutyIncidentResponse { private String status; private String message; private String dedupKey; @JsonProperty("incident_key") private String incidentKey; @JsonProperty("created_at") private String createdAt; private List<Error> errors; @Data public static class Error { private String code; private String message; private String field; } } // PagerDutyIncident.java @Data @Builder @AllArgsConstructor @NoArgsConstructor public class PagerDutyIncident { private String id; private String title; private String description; private IncidentSeverity severity; private String serviceId; private String assignedTo; private IncidentStatus status; private ZonedDateTime createdAt; private ZonedDateTime lastStatusChange; private Map<String, Object> customDetails; private List<String> escalationPolicies; private Integer urgency; public enum IncidentStatus { TRIGGERED, ACKNOWLEDGED, RESOLVED } } Core PagerDuty Service Implementation
1. Main PagerDuty Service
// PagerDutyService.java @Service @Slf4j public class PagerDutyService { private final PagerDutyConfig config; private final ObjectMapper objectMapper; private final RestTemplate restTemplate; private final PagerDutyEventV2Client eventClient; public PagerDutyService(PagerDutyConfig config, ObjectMapper objectMapper, RestTemplate restTemplate) { this.config = config; this.objectMapper = objectMapper; this.restTemplate = restTemplate; this.config.validate(); // Initialize Events API v2 client this.eventClient = new PagerDutyEventV2Client(config.getIntegrationKey()); } /** * Trigger a new incident in PagerDuty */ public IncidentTriggerResult triggerIncident(IncidentTriggerRequest request) { try { log.info("Triggering PagerDuty incident: {}", request.getSummary()); PagerDutyIncidentRequest incidentRequest = buildIncidentRequest(request); String requestBody = objectMapper.writeValueAsString(incidentRequest); HttpHeaders headers = createHeaders(); HttpEntity<String> entity = new HttpEntity<>(requestBody, headers); ResponseEntity<String> response = restTemplate.postForEntity( config.getApiUrl() + "/v2/enqueue", entity, String.class ); if (response.getStatusCode().is2xxSuccessful()) { PagerDutyIncidentResponse responseBody = objectMapper.readValue( response.getBody(), PagerDutyIncidentResponse.class); log.info("Successfully triggered incident with dedup key: {}", responseBody.getDedupKey()); return IncidentTriggerResult.builder() .success(true) .incidentKey(responseBody.getDedupKey()) .message("Incident triggered successfully") .build(); } else { log.error("Failed to trigger incident. Status: {}, Response: {}", response.getStatusCode(), response.getBody()); return IncidentTriggerResult.builder() .success(false) .message("Failed to trigger incident: " + response.getBody()) .build(); } } catch (Exception e) { log.error("Error triggering PagerDuty incident", e); return IncidentTriggerResult.builder() .success(false) .message("Error triggering incident: " + e.getMessage()) .build(); } } /** * Trigger incident using Events API v2 (Recommended) */ public IncidentTriggerResult triggerIncidentV2(IncidentTriggerRequest request) { try { EventResult result = eventClient.sendEvent(EventPayload.builder() .summary(request.getSummary()) .source(request.getSource()) .severity(request.getSeverity().getValue()) .timestamp(request.getTimestamp() != null ? request.getTimestamp() : Instant.now().toString()) .customDetails(request.getCustomDetails()) .component(request.getComponent()) .group(request.getGroup()) .eventClass(request.getEventClass()) .build()); if (result.isSuccess()) { log.info("Successfully triggered incident via V2 API. Dedup key: {}", result.getDedupKey()); return IncidentTriggerResult.builder() .success(true) .incidentKey(result.getDedupKey()) .message("Incident triggered successfully") .build(); } else { log.error("Failed to trigger incident via V2 API: {}", result.getMessage()); return IncidentTriggerResult.builder() .success(false) .message(result.getMessage()) .errors(result.getErrors()) .build(); } } catch (Exception e) { log.error("Error triggering PagerDuty incident via V2 API", e); return IncidentTriggerResult.builder() .success(false) .message("Error triggering incident: " + e.getMessage()) .build(); } } /** * Acknowledge an existing incident */ public boolean acknowledgeIncident(String incidentKey, String message) { try { PagerDutyIncidentRequest request = PagerDutyIncidentRequest.builder() .routingKey(config.getIntegrationKey()) .eventAction(IncidentAction.ACKNOWLEDGE.getValue()) .dedupKey(incidentKey) .payload(PagerDutyIncidentRequest.Payload.builder() .summary(message) .source(config.getFromEmail()) .build()) .build(); return sendEvent(request); } catch (Exception e) { log.error("Error acknowledging incident: {}", incidentKey, e); return false; } } /** * Resolve an existing incident */ public boolean resolveIncident(String incidentKey, String resolutionMessage) { try { PagerDutyIncidentRequest request = PagerDutyIncidentRequest.builder() .routingKey(config.getIntegrationKey()) .eventAction(IncidentAction.RESOLVE.getValue()) .dedupKey(incidentKey) .payload(PagerDutyIncidentRequest.Payload.builder() .summary(resolutionMessage) .source(config.getFromEmail()) .build()) .build(); return sendEvent(request); } catch (Exception e) { log.error("Error resolving incident: {}", incidentKey, e); return false; } } /** * Get incident details from PagerDuty REST API */ public Optional<PagerDutyIncident> getIncident(String incidentId) { try { HttpHeaders headers = createHeaders(); HttpEntity<Void> entity = new HttpEntity<>(headers); ResponseEntity<String> response = restTemplate.exchange( config.getApiUrl() + "/incidents/" + incidentId, HttpMethod.GET, entity, String.class ); if (response.getStatusCode().is2xxSuccessful()) { JsonNode root = objectMapper.readTree(response.getBody()); JsonNode incidentNode = root.path("incident"); PagerDutyIncident incident = objectMapper.treeToValue( incidentNode, PagerDutyIncident.class); return Optional.of(incident); } } catch (Exception e) { log.error("Error fetching incident: {}", incidentId, e); } return Optional.empty(); } /** * List incidents with filtering */ public List<PagerDutyIncident> listIncidents(IncidentFilter filter) { try { HttpHeaders headers = createHeaders(); HttpEntity<Void> entity = new HttpEntity<>(headers); UriComponentsBuilder builder = UriComponentsBuilder .fromHttpUrl(config.getApiUrl() + "/incidents") .queryParam("limit", filter.getLimit() != null ? filter.getLimit() : 25) .queryParam("offset", filter.getOffset() != null ? filter.getOffset() : 0); if (filter.getStatuses() != null && !filter.getStatuses().isEmpty()) { builder.queryParam("statuses", String.join(",", filter.getStatuses())); } if (filter.getServiceIds() != null && !filter.getServiceIds().isEmpty()) { builder.queryParam("service_ids", String.join(",", filter.getServiceIds())); } if (filter.getSince() != null) { builder.queryParam("since", filter.getSince()); } if (filter.getUntil() != null) { builder.queryParam("until", filter.getUntil()); } ResponseEntity<String> response = restTemplate.exchange( builder.toUriString(), HttpMethod.GET, entity, String.class ); if (response.getStatusCode().is2xxSuccessful()) { JsonNode root = objectMapper.readTree(response.getBody()); JsonNode incidentsNode = root.path("incidents"); List<PagerDutyIncident> incidents = new ArrayList<>(); for (JsonNode incidentNode : incidentsNode) { PagerDutyIncident incident = objectMapper.treeToValue( incidentNode, PagerDutyIncident.class); incidents.add(incident); } return incidents; } } catch (Exception e) { log.error("Error listing incidents", e); } return Collections.emptyList(); } private PagerDutyIncidentRequest buildIncidentRequest(IncidentTriggerRequest request) { return PagerDutyIncidentRequest.builder() .routingKey(config.getIntegrationKey()) .eventAction(IncidentAction.TRIGGER.getValue()) .dedupKey(request.getDedupKey() != null ? request.getDedupKey() : generateDedupKey(request)) .payload(PagerDutyIncidentRequest.Payload.builder() .summary(request.getSummary()) .source(request.getSource()) .severity(request.getSeverity().getValue()) .timestamp(request.getTimestamp() != null ? request.getTimestamp() : Instant.now().toString()) .customDetails(request.getCustomDetails()) .component(request.getComponent()) .group(request.getGroup()) .eventClass(request.getEventClass()) .build()) .build(); } private HttpHeaders createHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set("Authorization", "Token token=" + config.getApiKey()); headers.set("Accept", "application/vnd.pagerduty+json;version=2"); headers.set("From", config.getFromEmail()); return headers; } private boolean sendEvent(PagerDutyIncidentRequest request) { try { String requestBody = objectMapper.writeValueAsString(request); HttpHeaders headers = createHeaders(); HttpEntity<String> entity = new HttpEntity<>(requestBody, headers); ResponseEntity<String> response = restTemplate.postForEntity( config.getApiUrl() + "/v2/enqueue", entity, String.class ); return response.getStatusCode().is2xxSuccessful(); } catch (Exception e) { log.error("Error sending event to PagerDuty", e); return false; } } private String generateDedupKey(IncidentTriggerRequest request) { return request.getSource() + "-" + request.getComponent() + "-" + Instant.now().toEpochMilli(); } } 2. Supporting Classes
// IncidentTriggerRequest.java @Data @Builder @AllArgsConstructor @NoArgsConstructor public class IncidentTriggerRequest { @NotBlank private String summary; @NotBlank private String source; @NotNull private IncidentSeverity severity; private String component; private String group; private String eventClass; private String timestamp; private String dedupKey; @Singular private Map<String, Object> customDetails; private List<PagerDutyIncidentRequest.Image> images; private List<PagerDutyIncidentRequest.Link> links; } // IncidentTriggerResult.java @Data @Builder @AllArgsConstructor @NoArgsConstructor public class IncidentTriggerResult { private boolean success; private String incidentKey; private String message; private List<String> errors; public static IncidentTriggerResult success(String incidentKey) { return IncidentTriggerResult.builder() .success(true) .incidentKey(incidentKey) .message("Incident triggered successfully") .build(); } public static IncidentTriggerResult failure(String message) { return IncidentTriggerResult.builder() .success(false) .message(message) .build(); } } // IncidentFilter.java @Data @Builder @AllArgsConstructor @NoArgsConstructor public class IncidentFilter { private List<String> statuses; private List<String> serviceIds; private String since; private String until; private Integer limit; private Integer offset; private String sortBy; } Advanced Features
1. Incident Management with Retry Logic
// RetryablePagerDutyService.java @Service @Slf4j public class RetryablePagerDutyService { private final PagerDutyService pagerDutyService; private final RetryTemplate retryTemplate; public RetryablePagerDutyService(PagerDutyService pagerDutyService) { this.pagerDutyService = pagerDutyService; this.retryTemplate = createRetryTemplate(); } /** * Trigger incident with retry logic */ public IncidentTriggerResult triggerIncidentWithRetry(IncidentTriggerRequest request) { return retryTemplate.execute(context -> { log.info("Attempting to trigger incident (attempt {}): {}", context.getRetryCount() + 1, request.getSummary()); IncidentTriggerResult result = pagerDutyService.triggerIncidentV2(request); if (!result.isSuccess()) { log.warn("Failed to trigger incident on attempt {}. Retrying...", context.getRetryCount() + 1); throw new IncidentTriggerException("Failed to trigger incident: " + result.getMessage()); } return result; }); } /** * Resolve incident with retry logic */ public boolean resolveIncidentWithRetry(String incidentKey, String resolutionMessage) { return retryTemplate.execute(context -> { log.info("Attempting to resolve incident {} (attempt {})", incidentKey, context.getRetryCount() + 1); boolean success = pagerDutyService.resolveIncident(incidentKey, resolutionMessage); if (!success) { log.warn("Failed to resolve incident {} on attempt {}. Retrying...", incidentKey, context.getRetryCount() + 1); throw new IncidentResolutionException("Failed to resolve incident: " + incidentKey); } return true; }); } private RetryTemplate createRetryTemplate() { RetryTemplate template = new RetryTemplate(); ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); backOffPolicy.setInitialInterval(1000); backOffPolicy.setMultiplier(2); backOffPolicy.setMaxInterval(10000); SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); retryPolicy.setMaxAttempts(3); template.setBackOffPolicy(backOffPolicy); template.setRetryPolicy(retryPolicy); return template; } } // Custom Exceptions class IncidentTriggerException extends RuntimeException { public IncidentTriggerException(String message) { super(message); } } class IncidentResolutionException extends RuntimeException { public IncidentResolutionException(String message) { super(message); } } 2. Event-Driven Incident Management
// IncidentEventPublisher.java @Component @Slf4j public class IncidentEventPublisher { private final ApplicationEventPublisher eventPublisher; public IncidentEventPublisher(ApplicationEventPublisher eventPublisher) { this.eventPublisher = eventPublisher; } public void publishIncidentTriggered(IncidentTriggeredEvent event) { log.info("Publishing incident triggered event: {}", event.getIncidentKey()); eventPublisher.publishEvent(event); } public void publishIncidentResolved(IncidentResolvedEvent event) { log.info("Publishing incident resolved event: {}", event.getIncidentKey()); eventPublisher.publishEvent(event); } } // Incident Events @Data @Builder @AllArgsConstructor @NoArgsConstructor public class IncidentTriggeredEvent { private String incidentKey; private String summary; private IncidentSeverity severity; private String source; private ZonedDateTime timestamp; private Map<String, Object> customDetails; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public class IncidentResolvedEvent { private String incidentKey; private String resolutionMessage; private ZonedDateTime resolvedAt; private String resolvedBy; } // Incident Event Listeners @Component @Slf4j public class IncidentEventListener { private final PagerDutyService pagerDutyService; private final NotificationService notificationService; public IncidentEventListener(PagerDutyService pagerDutyService, NotificationService notificationService) { this.pagerDutyService = pagerDutyService; this.notificationService = notificationService; } @EventListener public void handleIncidentTriggered(IncidentTriggeredEvent event) { log.info("Handling incident triggered event: {}", event.getIncidentKey()); // Send additional notifications notificationService.sendIncidentNotification(event); // Log to audit system log.info("Incident {} triggered with severity {}", event.getIncidentKey(), event.getSeverity()); } @EventListener public void handleIncidentResolved(IncidentResolvedEvent event) { log.info("Handling incident resolved event: {}", event.getIncidentKey()); // Send resolution notifications notificationService.sendResolutionNotification(event); // Update internal tracking log.info("Incident {} resolved at {}", event.getIncidentKey(), event.getResolvedAt()); } } Spring Boot REST Controller
// IncidentController.java @RestController @RequestMapping("/api/v1/incidents") @Slf4j @Validated public class IncidentController { private final PagerDutyService pagerDutyService; private final RetryablePagerDutyService retryableService; private final IncidentEventPublisher eventPublisher; public IncidentController(PagerDutyService pagerDutyService, RetryablePagerDutyService retryableService, IncidentEventPublisher eventPublisher) { this.pagerDutyService = pagerDutyService; this.retryableService = retryableService; this.eventPublisher = eventPublisher; } @PostMapping("/trigger") public ResponseEntity<ApiResponse<IncidentTriggerResult>> triggerIncident( @Valid @RequestBody IncidentTriggerRequest request) { log.info("Received incident trigger request: {}", request.getSummary()); try { IncidentTriggerResult result = retryableService.triggerIncidentWithRetry(request); if (result.isSuccess()) { // Publish event for other systems eventPublisher.publishIncidentTriggered(IncidentTriggeredEvent.builder() .incidentKey(result.getIncidentKey()) .summary(request.getSummary()) .severity(request.getSeverity()) .source(request.getSource()) .timestamp(ZonedDateTime.now()) .customDetails(request.getCustomDetails()) .build()); return ResponseEntity.ok(ApiResponse.success(result)); } else { return ResponseEntity.badRequest() .body(ApiResponse.error(result.getMessage())); } } catch (Exception e) { log.error("Failed to trigger incident", e); return ResponseEntity.internalServerError() .body(ApiResponse.error("Failed to trigger incident: " + e.getMessage())); } } @PostMapping("/{incidentKey}/acknowledge") public ResponseEntity<ApiResponse<Void>> acknowledgeIncident( @PathVariable String incidentKey, @RequestParam(defaultValue = "Acknowledged via API") String message) { log.info("Acknowledging incident: {}", incidentKey); boolean success = pagerDutyService.acknowledgeIncident(incidentKey, message); if (success) { return ResponseEntity.ok(ApiResponse.success(null, "Incident acknowledged")); } else { return ResponseEntity.badRequest() .body(ApiResponse.error("Failed to acknowledge incident")); } } @PostMapping("/{incidentKey}/resolve") public ResponseEntity<ApiResponse<Void>> resolveIncident( @PathVariable String incidentKey, @RequestParam String resolutionMessage) { log.info("Resolving incident: {}", incidentKey); try { boolean success = retryableService.resolveIncidentWithRetry(incidentKey, resolutionMessage); if (success) { eventPublisher.publishIncidentResolved(IncidentResolvedEvent.builder() .incidentKey(incidentKey) .resolutionMessage(resolutionMessage) .resolvedAt(ZonedDateTime.now()) .resolvedBy("API") .build()); return ResponseEntity.ok(ApiResponse.success(null, "Incident resolved")); } else { return ResponseEntity.badRequest() .body(ApiResponse.error("Failed to resolve incident")); } } catch (Exception e) { log.error("Failed to resolve incident: {}", incidentKey, e); return ResponseEntity.internalServerError() .body(ApiResponse.error("Failed to resolve incident: " + e.getMessage())); } } @GetMapping("/{incidentId}") public ResponseEntity<ApiResponse<PagerDutyIncident>> getIncident( @PathVariable String incidentId) { Optional<PagerDutyIncident> incident = pagerDutyService.getIncident(incidentId); return incident.map(inc -> ResponseEntity.ok(ApiResponse.success(inc))) .orElse(ResponseEntity.notFound().build()); } @GetMapping public ResponseEntity<ApiResponse<List<PagerDutyIncident>>> listIncidents( @ModelAttribute IncidentFilter filter) { List<PagerDutyIncident> incidents = pagerDutyService.listIncidents(filter); return ResponseEntity.ok(ApiResponse.success(incidents)); } } // ApiResponse.java @Data @Builder @AllArgsConstructor @NoArgsConstructor public class ApiResponse<T> { private boolean success; private String message; private T data; private ZonedDateTime timestamp; private String requestId; public static <T> ApiResponse<T> success(T data) { return ApiResponse.<T>builder() .success(true) .data(data) .timestamp(ZonedDateTime.now()) .build(); } public static <T> ApiResponse<T> success(T data, String message) { return ApiResponse.<T>builder() .success(true) .message(message) .data(data) .timestamp(ZonedDateTime.now()) .build(); } public static <T> ApiResponse<T> error(String message) { return ApiResponse.<T>builder() .success(false) .message(message) .timestamp(ZonedDateTime.now()) .build(); } } Testing
1. Unit Tests
// PagerDutyServiceTest.java @ExtendWith(MockitoExtension.class) class PagerDutyServiceTest { @Mock private RestTemplate restTemplate; @Mock private ObjectMapper objectMapper; private PagerDutyService pagerDutyService; private PagerDutyConfig config; @BeforeEach void setUp() { config = new PagerDutyConfig(); config.setApiKey("test-api-key"); config.setIntegrationKey("test-integration-key"); config.setFromEmail("[email protected]"); config.setApiUrl("https://api.pagerduty.com"); pagerDutyService = new PagerDutyService(config, objectMapper, restTemplate); } @Test void testTriggerIncident_Success() throws Exception { // Given IncidentTriggerRequest request = IncidentTriggerRequest.builder() .summary("Test Incident") .source("test-service") .severity(IncidentSeverity.CRITICAL) .build(); PagerDutyIncidentRequest expectedRequest = createExpectedRequest(request); PagerDutyIncidentResponse mockResponse = new PagerDutyIncidentResponse(); mockResponse.setStatus("success"); mockResponse.setDedupKey("incident-123"); when(objectMapper.writeValueAsString(any())).thenReturn("request-body"); when(objectMapper.readValue(anyString(), eq(PagerDutyIncidentResponse.class))) .thenReturn(mockResponse); when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class))) .thenReturn(ResponseEntity.ok("response-body")); // When IncidentTriggerResult result = pagerDutyService.triggerIncident(request); // Then assertTrue(result.isSuccess()); assertEquals("incident-123", result.getIncidentKey()); verify(restTemplate).postForEntity(anyString(), any(HttpEntity.class), eq(String.class)); } @Test void testResolveIncident_Success() throws Exception { // Given String incidentKey = "incident-123"; String resolutionMessage = "Issue resolved"; when(objectMapper.writeValueAsString(any())).thenReturn("request-body"); when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class))) .thenReturn(ResponseEntity.ok("response-body")); // When boolean result = pagerDutyService.resolveIncident(incidentKey, resolutionMessage); // Then assertTrue(result); } private PagerDutyIncidentRequest createExpectedRequest(IncidentTriggerRequest request) { return PagerDutyIncidentRequest.builder() .routingKey(config.getIntegrationKey()) .eventAction(IncidentAction.TRIGGER.getValue()) .payload(PagerDutyIncidentRequest.Payload.builder() .summary(request.getSummary()) .source(request.getSource()) .severity(request.getSeverity().getValue()) .build()) .build(); } } 2. Integration Test with WireMock
// PagerDutyIntegrationTest.java @SpringBootTest @TestPropertySource(properties = { "pagerduty.api-key=test-api-key", "pagerduty.integration-key=test-integration-key", "[email protected]", "pagerduty.api-url=http://localhost:${wiremock.server.port}" }) @ExtendWith(SpringExtension.class) class PagerDutyIntegrationTest { @Autowired private PagerDutyService pagerDutyService; @RegisterExtension static WireMockExtension wireMock = WireMockExtension.newInstance() .options(wireMockConfig().dynamicPort()) .build(); @Test void testTriggerIncident_Integration() throws Exception { // Given String expectedResponse = """ { "status": "success", "message": "Event processed", "dedup_key": "test-incident-123" } """; wireMock.stubFor(post(urlEqualTo("/v2/enqueue")) .willReturn(aResponse() .withStatus(202) .withHeader("Content-Type", "application/json") .withBody(expectedResponse))); IncidentTriggerRequest request = IncidentTriggerRequest.builder() .summary("Integration Test Incident") .source("integration-test") .severity(IncidentSeverity.ERROR) .build(); // When IncidentTriggerResult result = pagerDutyService.triggerIncident(request); // Then assertTrue(result.isSuccess()); assertEquals("test-incident-123", result.getIncidentKey()); // Verify request was made wireMock.verify(postRequestedFor(urlEqualTo("/v2/enqueue")) .withHeader("Authorization", containing("test-api-key")) .withHeader("Content-Type", containing("application/json"))); } } Configuration
1. Application Properties
# application.yml pagerduty: api-key: ${PAGERDUTY_API_KEY:your-api-key-here} integration-key: ${PAGERDUTY_INTEGRATION_KEY:your-integration-key-here} api-url: https://api.pagerduty.com timeout-seconds: 30 max-retries: 3 from-email: [email protected] service-id: ${PAGERDUTY_SERVICE_ID:your-service-id} # RestTemplate configuration rest-template: connect-timeout: 5000 read-timeout: 10000 # Logging logging: level: com.yourcompany.pagerduty: DEBUG 2. Spring Configuration
// AppConfig.java @Configuration public class AppConfig { @Bean @ConfigurationProperties(prefix = "pagerduty") public PagerDutyConfig pagerDutyConfig() { return new PagerDutyConfig(); } @Bean public RestTemplate restTemplate(PagerDutyConfig config) { RestTemplate restTemplate = new RestTemplate(); // Configure timeouts SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(Duration.ofSeconds(config.getTimeoutSeconds())); factory.setReadTimeout(Duration.ofSeconds(config.getTimeoutSeconds())); restTemplate.setRequestFactory(factory); // Add error handler restTemplate.setErrorHandler(new CustomErrorHandler()); return restTemplate; } @Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); return mapper; } } // CustomErrorHandler.java @Slf4j public class CustomErrorHandler implements ResponseErrorHandler { @Override public boolean hasError(ClientHttpResponse response) throws IOException { return response.getStatusCode().isError(); } @Override public void handleError(ClientHttpResponse response) throws IOException { log.error("HTTP error occurred: {} {}", response.getStatusCode(), response.getStatusText()); throw new HttpClientErrorException(response.getStatusCode(), "PagerDuty API error: " + response.getStatusText()); } } This comprehensive PagerDuty integration provides:
- Incident triggering with proper error handling and retries
- Incident management (acknowledge, resolve)
- Event-driven architecture for extensibility
- Comprehensive testing with unit and integration tests
- Production-ready configuration with proper timeouts and retries
- REST API for easy integration with other systems
The implementation follows best practices for reliability, observability, and maintainability in production environments.