Introduction
PagerDuty is a popular incident management platform that helps teams respond to and resolve incidents quickly. This guide covers how to programmatically trigger, manage, and resolve PagerDuty incidents using Java.
Dependencies and Setup
1. Maven Dependencies
<properties>
<spring-boot.version>3.2.0</spring-boot.version>
<jackson.version>2.15.3</jackson.version>
<okhttp.version>4.12.0</okhttp.version>
</properties>
<dependencies>
<!-- Spring Boot Web for REST client -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.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>
<!-- Mock Server for Testing -->
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
<version>5.15.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Core PagerDuty Models
2. PagerDuty Data Models
package com.example.pagerduty.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PagerDutyEvent {
@JsonProperty("routing_key")
private String routingKey;
@JsonProperty("event_action")
private EventAction eventAction;
@JsonProperty("dedup_key")
private String dedupKey;
private Payload payload;
private List<Image> images;
private List<Link> links;
private Client client;
private Client clientUrl;
// Constructors
public PagerDutyEvent() {}
public PagerDutyEvent(String routingKey, EventAction eventAction, Payload payload) {
this.routingKey = routingKey;
this.eventAction = eventAction;
this.payload = payload;
}
// Getters and Setters
public String getRoutingKey() { return routingKey; }
public void setRoutingKey(String routingKey) { this.routingKey = routingKey; }
public EventAction getEventAction() { return eventAction; }
public void setEventAction(EventAction eventAction) { this.eventAction = eventAction; }
public String getDedupKey() { return dedupKey; }
public void setDedupKey(String dedupKey) { this.dedupKey = dedupKey; }
public Payload getPayload() { return payload; }
public void setPayload(Payload payload) { this.payload = payload; }
public List<Image> getImages() { return images; }
public void setImages(List<Image> images) { this.images = images; }
public List<Link> getLinks() { return links; }
public void setLinks(List<Link> links) { this.links = links; }
public Client getClient() { return client; }
public void setClient(Client client) { this.client = client; }
public Client getClientUrl() { return clientUrl; }
public void setClientUrl(Client clientUrl) { this.clientUrl = clientUrl; }
public enum EventAction {
trigger, acknowledge, resolve
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Payload {
private String summary;
private String source;
private Severity severity;
private String timestamp;
private String component;
private String group;
private String classType;
private Map<String, Object> customDetails;
// Constructors
public Payload() {}
public Payload(String summary, String source, Severity severity) {
this.summary = summary;
this.source = source;
this.severity = severity;
this.timestamp = OffsetDateTime.now().toString();
}
// Getters and Setters
public String getSummary() { return summary; }
public void setSummary(String summary) { this.summary = summary; }
public String getSource() { return source; }
public void setSource(String source) { this.source = source; }
public Severity getSeverity() { return severity; }
public void setSeverity(Severity severity) { this.severity = severity; }
public String getTimestamp() { return timestamp; }
public void setTimestamp(String timestamp) { this.timestamp = timestamp; }
public String getComponent() { return component; }
public void setComponent(String component) { this.component = component; }
public String getGroup() { return group; }
public void setGroup(String group) { this.group = group; }
@JsonProperty("class")
public String getClassType() { return classType; }
public void setClassType(String classType) { this.classType = classType; }
@JsonProperty("custom_details")
public Map<String, Object> getCustomDetails() { return customDetails; }
public void setCustomDetails(Map<String, Object> customDetails) { this.customDetails = customDetails; }
}
public enum Severity {
critical, error, warning, info
}
public static class Image {
private String src;
private String href;
private String alt;
// Constructors, getters, and setters
public Image() {}
public Image(String src, String alt) {
this.src = src;
this.alt = alt;
}
public String getSrc() { return src; }
public void setSrc(String src) { this.src = src; }
public String getHref() { return href; }
public void setHref(String href) { this.href = href; }
public String getAlt() { return alt; }
public void setAlt(String alt) { this.alt = alt; }
}
public static class Link {
private String href;
private String text;
// Constructors, getters, and setters
public Link() {}
public Link(String href, String text) {
this.href = href;
this.text = text;
}
public String getHref() { return href; }
public void setHref(String href) { this.href = href; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
}
public static class Client {
private String name;
private String type;
private String version;
// Constructors, getters, and setters
public Client() {}
public Client(String name) {
this.name = name;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
}
}
3. PagerDuty Response Models
package com.example.pagerduty.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class PagerDutyResponse {
private String status;
private String message;
@JsonProperty("dedup_key")
private String dedupKey;
private List<Error> errors;
// 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; }
public String getDedupKey() { return dedupKey; }
public void setDedupKey(String dedupKey) { this.dedupKey = dedupKey; }
public List<Error> getErrors() { return errors; }
public void setErrors(List<Error> errors) { this.errors = errors; }
public static class Error {
private String message;
private String code;
// Getters and Setters
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public String getCode() { return code; }
public void setCode(String code) { this.code = code; }
}
}
PagerDuty Client Implementation
4. PagerDuty Event Client
package com.example.pagerduty.client;
import com.example.pagerduty.model.PagerDutyEvent;
import com.example.pagerduty.model.PagerDutyResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
@Component
public class PagerDutyEventClient {
private static final Logger log = LoggerFactory.getLogger(PagerDutyEventClient.class);
private static final String EVENT_API_URL = "https://events.pagerduty.com/v2/enqueue";
private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json");
private final OkHttpClient httpClient;
private final ObjectMapper objectMapper;
private final String defaultRoutingKey;
public PagerDutyEventClient(
@Value("${pagerduty.routing-key}") String defaultRoutingKey,
ObjectMapper objectMapper) {
this.defaultRoutingKey = defaultRoutingKey;
this.objectMapper = objectMapper;
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build();
}
public PagerDutyResponse triggerIncident(PagerDutyEvent.Payload payload) {
return triggerIncident(payload, defaultRoutingKey, null);
}
public PagerDutyResponse triggerIncident(PagerDutyEvent.Payload payload, String dedupKey) {
return triggerIncident(payload, defaultRoutingKey, dedupKey);
}
public PagerDutyResponse triggerIncident(PagerDutyEvent.Payload payload,
String routingKey,
String dedupKey) {
PagerDutyEvent event = new PagerDutyEvent();
event.setRoutingKey(routingKey);
event.setEventAction(PagerDutyEvent.EventAction.trigger);
event.setPayload(payload);
event.setDedupKey(dedupKey);
return sendEvent(event);
}
public PagerDutyResponse acknowledgeIncident(String dedupKey) {
return acknowledgeIncident(dedupKey, defaultRoutingKey);
}
public PagerDutyResponse acknowledgeIncident(String dedupKey, String routingKey) {
PagerDutyEvent event = new PagerDutyEvent();
event.setRoutingKey(routingKey);
event.setEventAction(PagerDutyEvent.EventAction.acknowledge);
event.setDedupKey(dedupKey);
return sendEvent(event);
}
public PagerDutyResponse resolveIncident(String dedupKey) {
return resolveIncident(dedupKey, defaultRoutingKey);
}
public PagerDutyResponse resolveIncident(String dedupKey, String routingKey) {
PagerDutyEvent event = new PagerDutyEvent();
event.setRoutingKey(routingKey);
event.setEventAction(PagerDutyEvent.EventAction.resolve);
event.setDedupKey(dedupKey);
return sendEvent(event);
}
private PagerDutyResponse sendEvent(PagerDutyEvent event) {
try {
String jsonBody = objectMapper.writeValueAsString(event);
RequestBody body = RequestBody.create(jsonBody, JSON_MEDIA_TYPE);
Request request = new Request.Builder()
.url(EVENT_API_URL)
.post(body)
.addHeader("Content-Type", "application/json")
.build();
log.info("Sending PagerDuty event: {}", jsonBody);
try (Response response = httpClient.newCall(request).execute()) {
if (response.isSuccessful() && response.body() != null) {
String responseBody = response.body().string();
log.info("PagerDuty API response: {}", responseBody);
return objectMapper.readValue(responseBody, PagerDutyResponse.class);
} else {
log.error("PagerDuty API request failed. Code: {}, Message: {}",
response.code(), response.message());
throw new PagerDutyException("API request failed: " + response.code());
}
}
} catch (IOException e) {
log.error("Error sending PagerDuty event", e);
throw new PagerDutyException("Failed to send event", e);
}
}
public static class PagerDutyException extends RuntimeException {
public PagerDutyException(String message) {
super(message);
}
public PagerDutyException(String message, Throwable cause) {
super(message, cause);
}
}
}
5. PagerDuty REST API Client
package com.example.pagerduty.client;
import com.example.pagerduty.model.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Component
public class PagerDutyRestClient {
private static final Logger log = LoggerFactory.getLogger(PagerDutyRestClient.class);
private static final String BASE_URL = "https://api.pagerduty.com";
private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json");
private final OkHttpClient httpClient;
private final ObjectMapper objectMapper;
private final String apiToken;
public PagerDutyRestClient(
@Value("${pagerduty.api-token}") String apiToken,
ObjectMapper objectMapper) {
this.apiToken = apiToken;
this.objectMapper = objectMapper;
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build();
}
public IncidentList getIncidents(IncidentFilter filter) {
try {
HttpUrl.Builder urlBuilder = HttpUrl.parse(BASE_URL + "/incidents").newBuilder();
if (filter != null) {
if (filter.getStatuses() != null && !filter.getStatuses().isEmpty()) {
urlBuilder.addQueryParameter("statuses[]",
String.join(",", filter.getStatuses()));
}
if (filter.getSince() != null) {
urlBuilder.addQueryParameter("since", filter.getSince());
}
if (filter.getUntil() != null) {
urlBuilder.addQueryParameter("until", filter.getUntil());
}
if (filter.getLimit() != null) {
urlBuilder.addQueryParameter("limit", filter.getLimit().toString());
}
if (filter.getOffset() != null) {
urlBuilder.addQueryParameter("offset", filter.getOffset().toString());
}
}
Request request = new Request.Builder()
.url(urlBuilder.build())
.get()
.addHeader("Authorization", "Token token=" + apiToken)
.addHeader("Accept", "application/vnd.pagerduty+json;version=2")
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (response.isSuccessful() && response.body() != null) {
String responseBody = response.body().string();
return objectMapper.readValue(responseBody, IncidentList.class);
} else {
log.error("Failed to get incidents. Code: {}", response.code());
throw new PagerDutyException("Failed to get incidents: " + response.code());
}
}
} catch (IOException e) {
log.error("Error getting incidents", e);
throw new PagerDutyException("Failed to get incidents", e);
}
}
public Incident updateIncident(String incidentId, IncidentUpdate update) {
try {
String jsonBody = objectMapper.writeValueAsString(update);
RequestBody body = RequestBody.create(jsonBody, JSON_MEDIA_TYPE);
Request request = new Request.Builder()
.url(BASE_URL + "/incidents/" + incidentId)
.put(body)
.addHeader("Authorization", "Token token=" + apiToken)
.addHeader("Accept", "application/vnd.pagerduty+json;version=2")
.addHeader("Content-Type", "application/json")
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (response.isSuccessful() && response.body() != null) {
String responseBody = response.body().string();
return objectMapper.readValue(responseBody, Incident.class);
} else {
log.error("Failed to update incident. Code: {}", response.code());
throw new PagerDutyException("Failed to update incident: " + response.code());
}
}
} catch (IOException e) {
log.error("Error updating incident", e);
throw new PagerDutyException("Failed to update incident", e);
}
}
public static class IncidentFilter {
private List<String> statuses;
private String since;
private String until;
private Integer limit;
private Integer offset;
// Getters and Setters
public List<String> getStatuses() { return statuses; }
public void setStatuses(List<String> statuses) { this.statuses = statuses; }
public String getSince() { return since; }
public void setSince(String since) { this.since = since; }
public String getUntil() { return until; }
public void setUntil(String until) { this.until = until; }
public Integer getLimit() { return limit; }
public void setLimit(Integer limit) { this.limit = limit; }
public Integer getOffset() { return offset; }
public void setOffset(Integer offset) { this.offset = offset; }
}
}
Incident Management Service
6. Incident Service
package com.example.pagerduty.service;
import com.example.pagerduty.client.PagerDutyEventClient;
import com.example.pagerduty.client.PagerDutyRestClient;
import com.example.pagerduty.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Service
public class IncidentService {
private static final Logger log = LoggerFactory.getLogger(IncidentService.class);
private final PagerDutyEventClient eventClient;
private final PagerDutyRestClient restClient;
public IncidentService(PagerDutyEventClient eventClient, PagerDutyRestClient restClient) {
this.eventClient = eventClient;
this.restClient = restClient;
}
public String triggerCriticalIncident(String summary, String source,
String component, Map<String, Object> details) {
PagerDutyEvent.Payload payload = createPayload(summary, source,
PagerDutyEvent.Severity.critical, component, details);
String dedupKey = generateDedupKey(source, component);
PagerDutyResponse response = eventClient.triggerIncident(payload, dedupKey);
log.info("Triggered critical incident. Dedup Key: {}, Response: {}",
dedupKey, response.getStatus());
return dedupKey;
}
public String triggerErrorIncident(String summary, String source,
String component, Map<String, Object> details) {
PagerDutyEvent.Payload payload = createPayload(summary, source,
PagerDutyEvent.Severity.error, component, details);
String dedupKey = generateDedupKey(source, component);
PagerDutyResponse response = eventClient.triggerIncident(payload, dedupKey);
log.info("Triggered error incident. Dedup Key: {}, Response: {}",
dedupKey, response.getStatus());
return dedupKey;
}
public String triggerWarningIncident(String summary, String source,
String component, Map<String, Object> details) {
PagerDutyEvent.Payload payload = createPayload(summary, source,
PagerDutyEvent.Severity.warning, component, details);
String dedupKey = generateDedupKey(source, component);
PagerDutyResponse response = eventClient.triggerIncident(payload, dedupKey);
log.info("Triggered warning incident. Dedup Key: {}, Response: {}",
dedupKey, response.getStatus());
return dedupKey;
}
public void acknowledgeIncident(String dedupKey) {
PagerDutyResponse response = eventClient.acknowledgeIncident(dedupKey);
log.info("Acknowledged incident. Dedup Key: {}, Response: {}",
dedupKey, response.getStatus());
}
public void resolveIncident(String dedupKey) {
PagerDutyResponse response = eventClient.resolveIncident(dedupKey);
log.info("Resolved incident. Dedup Key: {}, Response: {}",
dedupKey, response.getStatus());
}
public void resolveIncidentWithNote(String dedupKey, String resolutionNote) {
// Add resolution details to custom details
Map<String, Object> resolutionDetails = new HashMap<>();
resolutionDetails.put("resolution_note", resolutionNote);
resolutionDetails.put("resolved_at", OffsetDateTime.now().toString());
// For resolution, we might want to use REST API for more control
// For Events API, we can only resolve with dedup key
PagerDutyResponse response = eventClient.resolveIncident(dedupKey);
log.info("Resolved incident with note. Dedup Key: {}, Response: {}",
dedupKey, response.getStatus());
}
public IncidentList getActiveIncidents() {
PagerDutyRestClient.IncidentFilter filter = new PagerDutyRestClient.IncidentFilter();
filter.setStatuses(List.of("triggered", "acknowledged"));
return restClient.getIncidents(filter);
}
public IncidentList getResolvedIncidents(String since) {
PagerDutyRestClient.IncidentFilter filter = new PagerDutyRestClient.IncidentFilter();
filter.setStatuses(List.of("resolved"));
filter.setSince(since);
return restClient.getIncidents(filter);
}
private PagerDutyEvent.Payload createPayload(String summary, String source,
PagerDutyEvent.Severity severity,
String component, Map<String, Object> details) {
PagerDutyEvent.Payload payload = new PagerDutyEvent.Payload(summary, source, severity);
payload.setComponent(component);
payload.setCustomDetails(details);
payload.setTimestamp(OffsetDateTime.now().toString());
return payload;
}
private String generateDedupKey(String source, String component) {
return source + "-" + component + "-" + UUID.randomUUID().toString().substring(0, 8);
}
}
7. Alert Builder Pattern
package com.example.pagerduty.builder;
import com.example.pagerduty.model.PagerDutyEvent;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AlertBuilder {
private String summary;
private String source;
private PagerDutyEvent.Severity severity;
private String component;
private String group;
private String classType;
private Map<String, Object> customDetails = new HashMap<>();
private List<PagerDutyEvent.Image> images = new ArrayList<>();
private List<PagerDutyEvent.Link> links = new ArrayList<>();
public AlertBuilder summary(String summary) {
this.summary = summary;
return this;
}
public AlertBuilder source(String source) {
this.source = source;
return this;
}
public AlertBuilder severity(PagerDutyEvent.Severity severity) {
this.severity = severity;
return this;
}
public AlertBuilder component(String component) {
this.component = component;
return this;
}
public AlertBuilder group(String group) {
this.group = group;
return this;
}
public AlertBuilder classType(String classType) {
this.classType = classType;
return this;
}
public AlertBuilder addCustomDetail(String key, Object value) {
this.customDetails.put(key, value);
return this;
}
public AlertBuilder addImage(String src, String alt) {
this.images.add(new PagerDutyEvent.Image(src, alt));
return this;
}
public AlertBuilder addLink(String href, String text) {
this.links.add(new PagerDutyEvent.Link(href, text));
return this;
}
public PagerDutyEvent.Payload build() {
PagerDutyEvent.Payload payload = new PagerDutyEvent.Payload();
payload.setSummary(summary);
payload.setSource(source);
payload.setSeverity(severity);
payload.setComponent(component);
payload.setGroup(group);
payload.setClassType(classType);
payload.setCustomDetails(customDetails);
payload.setTimestamp(OffsetDateTime.now().toString());
return payload;
}
// Convenience methods for common alert types
public static AlertBuilder critical(String summary, String source) {
return new AlertBuilder()
.summary(summary)
.source(source)
.severity(PagerDutyEvent.Severity.critical);
}
public static AlertBuilder error(String summary, String source) {
return new AlertBuilder()
.summary(summary)
.source(source)
.severity(PagerDutyEvent.Severity.error);
}
public static AlertBuilder warning(String summary, String source) {
return new AlertBuilder()
.summary(summary)
.source(source)
.severity(PagerDutyEvent.Severity.warning);
}
public static AlertBuilder info(String summary, String source) {
return new AlertBuilder()
.summary(summary)
.source(source)
.severity(PagerDutyEvent.Severity.info);
}
}
Integration with Monitoring Systems
8. Spring Boot Actuator Integration
package com.example.pagerduty.actuator;
import com.example.pagerduty.service.IncidentService;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class PagerDutyHealthIndicator implements HealthIndicator {
private final IncidentService incidentService;
public PagerDutyHealthIndicator(IncidentService incidentService) {
this.incidentService = incidentService;
}
@Override
public Health health() {
try {
// Check if we can retrieve incidents (tests API connectivity)
var incidents = incidentService.getActiveIncidents();
return Health.up()
.withDetail("activeIncidents", incidents.getIncidents().size())
.withDetail("apiStatus", "connected")
.build();
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.withDetail("apiStatus", "disconnected")
.build();
}
}
}
9. Application Event Listener for Automated Alerts
package com.example.pagerduty.listener;
import com.example.pagerduty.service.IncidentService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class ApplicationAlertListener {
private static final Logger log = LoggerFactory.getLogger(ApplicationAlertListener.class);
private final IncidentService incidentService;
public ApplicationAlertListener(IncidentService incidentService) {
this.incidentService = incidentService;
}
@Async
@EventListener
public void handleDatabaseConnectionFailure(DatabaseConnectionFailureEvent event) {
Map<String, Object> details = new HashMap<>();
details.put("database_url", event.getDatabaseUrl());
details.put("error_message", event.getErrorMessage());
details.put("retry_count", event.getRetryCount());
String dedupKey = incidentService.triggerCriticalIncident(
"Database Connection Failure - " + event.getDatabaseUrl(),
"database-service",
"database-connection",
details
);
log.warn("Triggered PagerDuty incident for database failure: {}", dedupKey);
}
@Async
@EventListener
public void handleHighMemoryUsage(HighMemoryUsageEvent event) {
Map<String, Object> details = new HashMap<>();
details.put("memory_usage_percent", event.getMemoryUsagePercent());
details.put("jvm_max_memory", event.getJvmMaxMemory());
details.put("jvm_used_memory", event.getJvmUsedMemory());
String dedupKey = incidentService.triggerWarningIncident(
"High Memory Usage - " + event.getMemoryUsagePercent() + "%",
"application-service",
"memory-usage",
details
);
log.warn("Triggered PagerDuty incident for high memory usage: {}", dedupKey);
}
@Async
@EventListener
public void handleExternalServiceFailure(ExternalServiceFailureEvent event) {
Map<String, Object> details = new HashMap<>();
details.put("service_url", event.getServiceUrl());
details.put("http_status", event.getHttpStatus());
details.put("response_time", event.getResponseTime());
details.put("error_message", event.getErrorMessage());
String dedupKey = incidentService.triggerErrorIncident(
"External Service Failure - " + event.getServiceName(),
"external-service",
event.getServiceName(),
details
);
log.warn("Triggered PagerDuty incident for external service failure: {}", dedupKey);
}
// Event classes
public static class DatabaseConnectionFailureEvent {
private final String databaseUrl;
private final String errorMessage;
private final int retryCount;
public DatabaseConnectionFailureEvent(String databaseUrl, String errorMessage, int retryCount) {
this.databaseUrl = databaseUrl;
this.errorMessage = errorMessage;
this.retryCount = retryCount;
}
// Getters
public String getDatabaseUrl() { return databaseUrl; }
public String getErrorMessage() { return errorMessage; }
public int getRetryCount() { return retryCount; }
}
public static class HighMemoryUsageEvent {
private final double memoryUsagePercent;
private final long jvmMaxMemory;
private final long jvmUsedMemory;
public HighMemoryUsageEvent(double memoryUsagePercent, long jvmMaxMemory, long jvmUsedMemory) {
this.memoryUsagePercent = memoryUsagePercent;
this.jvmMaxMemory = jvmMaxMemory;
this.jvmUsedMemory = jvmUsedMemory;
}
// Getters
public double getMemoryUsagePercent() { return memoryUsagePercent; }
public long getJvmMaxMemory() { return jvmMaxMemory; }
public long getJvmUsedMemory() { return jvmUsedMemory; }
}
public static class ExternalServiceFailureEvent {
private final String serviceName;
private final String serviceUrl;
private final Integer httpStatus;
private final Long responseTime;
private final String errorMessage;
public ExternalServiceFailureEvent(String serviceName, String serviceUrl,
Integer httpStatus, Long responseTime, String errorMessage) {
this.serviceName = serviceName;
this.serviceUrl = serviceUrl;
this.httpStatus = httpStatus;
this.responseTime = responseTime;
this.errorMessage = errorMessage;
}
// Getters
public String getServiceName() { return serviceName; }
public String getServiceUrl() { return serviceUrl; }
public Integer getHttpStatus() { return httpStatus; }
public Long getResponseTime() { return responseTime; }
public String getErrorMessage() { return errorMessage; }
}
}
REST API Controller
10. Incident Management Controller
package com.example.pagerduty.controller;
import com.example.pagerduty.builder.AlertBuilder;
import com.example.pagerduty.model.PagerDutyEvent;
import com.example.pagerduty.service.IncidentService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/incidents")
public class IncidentController {
private final IncidentService incidentService;
public IncidentController(IncidentService incidentService) {
this.incidentService = incidentService;
}
@PostMapping("/trigger")
public ResponseEntity<Map<String, String>> triggerIncident(
@RequestBody TriggerIncidentRequest request) {
String dedupKey = incidentService.triggerCriticalIncident(
request.getSummary(),
request.getSource(),
request.getComponent(),
request.getDetails()
);
return ResponseEntity.ok(Map.of("dedupKey", dedupKey, "status", "triggered"));
}
@PostMapping("/trigger-with-builder")
public ResponseEntity<Map<String, String>> triggerIncidentWithBuilder(
@RequestBody BuilderIncidentRequest request) {
AlertBuilder builder = AlertBuilder.critical(request.getSummary(), request.getSource())
.component(request.getComponent())
.group(request.getGroup())
.classType(request.getClassType());
if (request.getDetails() != null) {
request.getDetails().forEach(builder::addCustomDetail);
}
PagerDutyEvent.Payload payload = builder.build();
String dedupKey = incidentService.triggerCriticalIncident(
payload.getSummary(),
payload.getSource(),
payload.getComponent(),
payload.getCustomDetails()
);
return ResponseEntity.ok(Map.of("dedupKey", dedupKey, "status", "triggered"));
}
@PostMapping("/{dedupKey}/acknowledge")
public ResponseEntity<Map<String, String>> acknowledgeIncident(
@PathVariable String dedupKey) {
incidentService.acknowledgeIncident(dedupKey);
return ResponseEntity.ok(Map.of("dedupKey", dedupKey, "status", "acknowledged"));
}
@PostMapping("/{dedupKey}/resolve")
public ResponseEntity<Map<String, String>> resolveIncident(
@PathVariable String dedupKey,
@RequestBody(required = false) ResolveIncidentRequest request) {
if (request != null && request.getResolutionNote() != null) {
incidentService.resolveIncidentWithNote(dedupKey, request.getResolutionNote());
} else {
incidentService.resolveIncident(dedupKey);
}
return ResponseEntity.ok(Map.of("dedupKey", dedupKey, "status", "resolved"));
}
@GetMapping("/active")
public ResponseEntity<?> getActiveIncidents() {
return ResponseEntity.ok(incidentService.getActiveIncidents());
}
// Request DTOs
public static class TriggerIncidentRequest {
private String summary;
private String source;
private String component;
private Map<String, Object> details;
// Getters and Setters
public String getSummary() { return summary; }
public void setSummary(String summary) { this.summary = summary; }
public String getSource() { return source; }
public void setSource(String source) { this.source = source; }
public String getComponent() { return component; }
public void setComponent(String component) { this.component = component; }
public Map<String, Object> getDetails() { return details; }
public void setDetails(Map<String, Object> details) { this.details = details; }
}
public static class BuilderIncidentRequest {
private String summary;
private String source;
private String component;
private String group;
private String classType;
private Map<String, Object> details;
// Getters and Setters
public String getSummary() { return summary; }
public void setSummary(String summary) { this.summary = summary; }
public String getSource() { return source; }
public void setSource(String source) { this.source = source; }
public String getComponent() { return component; }
public void setComponent(String component) { this.component = component; }
public String getGroup() { return group; }
public void setGroup(String group) { this.group = group; }
public String getClassType() { return classType; }
public void setClassType(String classType) { this.classType = classType; }
public Map<String, Object> getDetails() { return details; }
public void setDetails(Map<String, Object> details) { this.details = details; }
}
public static class ResolveIncidentRequest {
private String resolutionNote;
// Getters and Setters
public String getResolutionNote() { return resolutionNote; }
public void setResolutionNote(String resolutionNote) { this.resolutionNote = resolutionNote; }
}
}
Testing
11. Unit Tests
package com.example.pagerduty.test;
import com.example.pagerduty.client.PagerDutyEventClient;
import com.example.pagerduty.model.PagerDutyEvent;
import com.example.pagerduty.model.PagerDutyResponse;
import com.example.pagerduty.service.IncidentService;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import java.io.IOException;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class IncidentServiceTest {
private MockWebServer mockWebServer;
private IncidentService incidentService;
private PagerDutyEventClient eventClient;
private ObjectMapper objectMapper;
@BeforeEach
void setUp() throws IOException {
mockWebServer = new MockWebServer();
mockWebServer.start();
objectMapper = new ObjectMapper();
eventClient = new PagerDutyEventClient("test-routing-key", objectMapper);
incidentService = new IncidentService(eventClient, null);
}
@AfterEach
void tearDown() throws IOException {
mockWebServer.shutdown();
}
@Test
void testTriggerCriticalIncident() throws Exception {
// Given
PagerDutyResponse mockResponse = new PagerDutyResponse();
mockResponse.setStatus("success");
mockResponse.setMessage("Event processed");
mockResponse.setDedupKey("test-dedup-key");
mockWebServer.enqueue(new MockResponse()
.setResponseCode(202)
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.setBody(objectMapper.writeValueAsString(mockResponse)));
// When
String dedupKey = incidentService.triggerCriticalIncident(
"Test critical incident",
"test-service",
"test-component",
Map.of("key", "value")
);
// Then
assertThat(dedupKey).isNotNull();
RecordedRequest recordedRequest = mockWebServer.takeRequest();
assertThat(recordedRequest.getMethod()).isEqualTo("POST");
assertThat(recordedRequest.getPath()).isEqualTo("/v2/enqueue");
PagerDutyEvent sentEvent = objectMapper.readValue(
recordedRequest.getBody().readUtf8(), PagerDutyEvent.class);
assertThat(sentEvent.getEventAction()).isEqualTo(PagerDutyEvent.EventAction.trigger);
assertThat(sentEvent.getPayload().getSeverity()).isEqualTo(PagerDutyEvent.Severity.critical);
}
}
Configuration
12. Application Configuration
# application.yml
pagerduty:
routing-key: ${PAGERDUTY_ROUTING_KEY:your-integration-key-here}
api-token: ${PAGERDUTY_API_TOKEN:your-api-token-here}
logging:
level:
com.example.pagerduty: DEBUG
server:
port: 8080
management:
endpoints:
web:
exposure:
include: health,metrics,info
endpoint:
health:
show-details: always
Usage Examples
13. Example Usage in Application Code
package com.example.pagerduty.example;
import com.example.pagerduty.builder.AlertBuilder;
import com.example.pagerduty.service.IncidentService;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class ApplicationMonitor {
private final IncidentService incidentService;
public ApplicationMonitor(IncidentService incidentService) {
this.incidentService = incidentService;
}
public void handleDatabaseOutage(String databaseUrl, String errorMessage) {
Map<String, Object> details = Map.of(
"database_url", databaseUrl,
"error_message", errorMessage,
"retry_attempts", 3,
"last_attempt", System.currentTimeMillis()
);
incidentService.triggerCriticalIncident(
"Database Service Unavailable",
"database-service",
"primary-database",
details
);
}
public void handleHighLatency(String serviceName, long latencyMs) {
AlertBuilder builder = AlertBuilder.warning(
"High latency detected in " + serviceName,
"performance-monitor"
)
.component(serviceName)
.group("performance")
.classType("high_latency")
.addCustomDetail("latency_ms", latencyMs)
.addCustomDetail("threshold_ms", 1000)
.addCustomDetail("service_endpoint", "/api/v1/process")
.addLink("https://grafana.example.com/dashboard", "Performance Dashboard");
// Using builder pattern for more complex alerts
PagerDutyEvent.Payload payload = builder.build();
incidentService.triggerWarningIncident(
payload.getSummary(),
payload.getSource(),
payload.getComponent(),
payload.getCustomDetails()
);
}
}
Best Practices
- Use Dedup Keys: Always use dedup keys to prevent duplicate incidents
- Meaningful Summaries: Create clear, actionable incident summaries
- Structured Details: Include relevant context in custom details
- Proper Severity: Use appropriate severity levels (critical, error, warning, info)
- Timely Resolution: Always resolve incidents when the issue is fixed
- Error Handling: Implement robust error handling for API failures
- Monitoring: Monitor your PagerDuty integration health
- Testing: Test your incident triggering in non-production environments
Conclusion
This comprehensive PagerDuty integration provides:
- Event API Client: For triggering, acknowledging, and resolving incidents
- REST API Client: For managing and querying incidents
- Builder Pattern: For creating complex alerts with rich context
- Spring Integration: For seamless integration with Spring Boot applications
- Monitoring: Health checks and automated alerting
- Testing: Comprehensive test coverage
The implementation follows best practices for incident management and provides a robust foundation for integrating PagerDuty into your Java applications.