In modern DevOps practices, quality gates are essential checkpoints that automatically validate whether a release meets predefined quality standards before progressing to the next environment. Keptn is a cloud-native control plane that automates quality gates, deployment orchestration, and reliability testing, helping Java teams deliver software with confidence.
What are Quality Gates?
Quality gates are automated checks that validate:
- Performance metrics (response time, throughput)
- Reliability indicators (error rates, availability)
- Security scans (vulnerabilities, compliance)
- Business metrics (conversion rates, user impact)
- Test results (coverage, pass rates)
Keptn Architecture for Quality Gates
[Java App] → [CI/CD Pipeline] → [Keptn] → [Monitoring Tools] → [Quality Gate] | | | | | Deploy Trigger Orchestrate Collect metrics Evaluate against new version evaluation tests & from Prometheus, SLOs and make process analysis Dynatrace, etc. go/no-go decision
Hands-On Tutorial: Implementing Keptn Quality Gates for Java Microservices
Let's build a complete quality gate system for a Java microservices ecosystem using Keptn, SLI/SLO definitions, and automated validation.
Step 1: Project Setup and Dependencies
Maven Dependencies (pom.xml):
<properties>
<spring-boot.version>3.2.0</spring-boot.version>
<micrometer.version>1.12.0</micrometer.version>
<resilience4j.version>2.2.0</resilience4j.version>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Monitoring & Metrics -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>${micrometer.version}</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>${micrometer.version}</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
<version>${micrometer.version}</version>
</dependency>
<!-- Resilience -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>${resilience4j.version}</version>
</dependency>
<!-- Keptn API Client -->
<dependency>
<groupId>io.keptn</groupId>
<artifactId>keptn-api-client</artifactId>
<version>0.1.0</version>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</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
application.yml:
# Keptn Configuration
keptn:
api:
url: ${KEPTN_API_URL:http://localhost:8080/api}
token: ${KEPTN_API_TOKEN}
project: ecommerce-platform
service: order-service
stage: production
# Monitoring
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
metrics:
enabled: true
metrics:
export:
prometheus:
enabled: true
distribution:
percentiles-histogram:
http.server.requests: true
tags:
application: order-service
environment: production
# SLO Configuration
slo:
thresholds:
response-time-p95: 500 # ms
error-rate: 0.05 # 5%
throughput-min: 10 # requests per second
availability: 0.995 # 99.5%
# Resilience4j
resilience4j:
circuitbreaker:
instances:
orderService:
register-health-indicator: true
sliding-window-size: 10
failure-rate-threshold: 50
wait-duration-in-open-state: 10s
timelimiter:
instances:
orderService:
timeout-duration: 5s
# Custom metrics
app:
metrics:
prefix: ecommerce
tags:
team: platform-engineering
version: ${app.version:1.0.0}
Step 3: Keptn Client Service
@Service
@Slf4j
public class KeptnService {
private final WebClient webClient;
private final KeptnConfig keptnConfig;
private final ObjectMapper objectMapper;
public KeptnService(WebClient.Builder webClientBuilder,
KeptnConfig keptnConfig,
ObjectMapper objectMapper) {
this.keptnConfig = keptnConfig;
this.objectMapper = objectMapper;
this.webClient = webClientBuilder
.baseUrl(keptnConfig.getApi().getUrl())
.defaultHeader("Content-Type", "application/json")
.defaultHeader("Authorization", "Bearer " + keptnConfig.getApi().getToken())
.build();
}
/**
* Send start evaluation event to Keptn
*/
public EvaluationStartResponse startEvaluation(EvaluationStartRequest request) {
try {
String url = "/v1/event";
CloudEvent<EvaluationData> cloudEvent = createCloudEvent(
"sh.keptn.event." + request.getStage() + ".evaluation.triggered",
request.getProject(),
request.getService(),
request.getStage(),
new EvaluationData(request)
);
return webClient.post()
.uri(url)
.bodyValue(cloudEvent)
.retrieve()
.bodyToMono(EvaluationStartResponse.class)
.block();
} catch (Exception e) {
log.error("Failed to start Keptn evaluation", e);
throw new KeptnException("Evaluation start failed", e);
}
}
/**
* Get evaluation results
*/
public EvaluationResult getEvaluationResult(String keptnContext, String evaluationId) {
try {
String url = "/v1/evaluation/{keptnContext}/{evaluationId}";
return webClient.get()
.uri(url, keptnContext, evaluationId)
.retrieve()
.bodyToMono(EvaluationResult.class)
.block();
} catch (Exception e) {
log.error("Failed to get evaluation result", e);
throw new KeptnException("Evaluation result retrieval failed", e);
}
}
/**
* Send deployment finished event
*/
public void sendDeploymentFinished(String project, String service, String stage,
boolean deploymentSuccess) {
try {
String eventType = deploymentSuccess ?
"sh.keptn.event." + stage + ".deployment.finished" :
"sh.keptn.event." + stage + ".deployment.failed";
DeploymentData data = new DeploymentData(project, service, stage, deploymentSuccess);
CloudEvent<DeploymentData> cloudEvent = createCloudEvent(
eventType, project, service, stage, data
);
webClient.post()
.uri("/v1/event")
.bodyValue(cloudEvent)
.retrieve()
.bodyToMono(String.class)
.block();
log.info("Sent deployment finished event for {}/{}/{} - success: {}",
project, service, stage, deploymentSuccess);
} catch (Exception e) {
log.error("Failed to send deployment finished event", e);
}
}
/**
* Send test finished event
*/
public void sendTestFinished(String project, String service, String stage,
TestResult testResult) {
try {
TestData data = new TestData(project, service, stage, testResult);
CloudEvent<TestData> cloudEvent = createCloudEvent(
"sh.keptn.event." + stage + ".tests.finished",
project, service, stage, data
);
webClient.post()
.uri("/v1/event")
.bodyValue(cloudEvent)
.retrieve()
.bodyToMono(String.class)
.block();
log.info("Sent test finished event for {}/{}/{} - passed: {}",
project, service, stage, testResult.isPassed());
} catch (Exception e) {
log.error("Failed to send test finished event", e);
}
}
private <T> CloudEvent<T> createCloudEvent(String type, String project,
String service, String stage, T data) {
return CloudEvent.<T>builder()
.specVersion("1.0")
.id(UUID.randomUUID().toString())
.type(type)
.source("java-application")
.time(Instant.now().toString())
.data(data)
.put("project", project)
.put("stage", stage)
.put("service", service)
.build();
}
// DTO Classes
@Data
public static class EvaluationStartRequest {
private String project;
private String service;
private String stage;
private LocalDateTime start;
private LocalDateTime end;
private Map<String, String> labels;
public EvaluationStartRequest(String project, String service, String stage,
LocalDateTime start, LocalDateTime end) {
this.project = project;
this.service = service;
this.stage = stage;
this.start = start;
this.end = end;
this.labels = new HashMap<>();
}
}
@Data
public static class EvaluationData {
private EvaluationStartRequest data;
public EvaluationData(EvaluationStartRequest request) {
this.data = request;
}
}
@Data
public static class DeploymentData {
private String project;
private String service;
private String stage;
private boolean deploymentSuccess;
private String message;
public DeploymentData(String project, String service, String stage, boolean deploymentSuccess) {
this.project = project;
this.service = service;
this.stage = stage;
this.deploymentSuccess = deploymentSuccess;
this.message = deploymentSuccess ? "Deployment completed successfully" : "Deployment failed";
}
}
@Data
public static class TestData {
private String project;
private String service;
private String stage;
private TestResult testResult;
public TestData(String project, String service, String stage, TestResult testResult) {
this.project = project;
this.service = service;
this.stage = stage;
this.testResult = testResult;
}
}
}
// CloudEvent wrapper for Keptn
@Data
@Builder
class CloudEvent<T> {
private String specVersion;
private String id;
private String type;
private String source;
private String time;
private T data;
private Map<String, String> extensions;
public void put(String key, String value) {
if (extensions == null) {
extensions = new HashMap<>();
}
extensions.put(key, value);
}
}
Step 4: SLO (Service Level Objective) Management
@Service
@Slf4j
public class SLOService {
private final MeterRegistry meterRegistry;
private final SLOConfig sloConfig;
private final Counter totalRequests;
private final Counter failedRequests;
private final Timer responseTimeTimer;
private final Gauge availabilityGauge;
private final DistributionSummary responseTimeDistribution;
public SLOService(MeterRegistry meterRegistry, SLOConfig sloConfig) {
this.meterRegistry = meterRegistry;
this.sloConfig = sloConfig;
// Initialize metrics
this.totalRequests = Counter.builder("http.requests.total")
.description("Total HTTP requests")
.register(meterRegistry);
this.failedRequests = Counter.builder("http.requests.failed")
.description("Failed HTTP requests")
.register(meterRegistry);
this.responseTimeTimer = Timer.builder("http.response.time")
.description("HTTP response time")
.publishPercentiles(0.5, 0.95, 0.99)
.register(meterRegistry);
this.responseTimeDistribution = DistributionSummary.builder("http.response.time.distribution")
.description("HTTP response time distribution")
.serviceLevelObjectives(
Duration.ofMillis(100).toMillis(),
Duration.ofMillis(500).toMillis(),
Duration.ofMillis(1000).toMillis(),
Duration.ofMillis(5000).toMillis()
)
.register(meterRegistry);
this.availabilityGauge = Gauge.builder("service.availability")
.description("Service availability percentage")
.register(meterRegistry);
}
/**
* Record HTTP request metrics for SLO calculation
*/
public void recordHttpRequest(String method, String endpoint, int statusCode,
long durationMs, boolean success) {
try {
totalRequests.increment();
if (!success || statusCode >= 500) {
failedRequests.increment();
}
responseTimeTimer.record(durationMs, TimeUnit.MILLISECONDS);
responseTimeDistribution.record(durationMs);
// Update availability gauge
updateAvailabilityGauge();
} catch (Exception e) {
log.error("Failed to record HTTP metrics", e);
}
}
/**
* Record business metrics for SLOs
*/
public void recordBusinessMetric(String metricName, double value, Map<String, String> tags) {
try {
Counter.builder("business.metric." + metricName)
.tags(tags)
.register(meterRegistry)
.increment(value);
} catch (Exception e) {
log.error("Failed to record business metric: {}", metricName, e);
}
}
/**
* Calculate current SLO compliance
*/
public SLOComplianceResult calculateSLOCompliance() {
SLOComplianceResult result = new SLOComplianceResult();
result.setTimestamp(Instant.now());
try {
// Calculate error rate
double total = totalRequests.count();
double failed = failedRequests.count();
double errorRate = total > 0 ? failed / total : 0.0;
result.setErrorRate(errorRate);
result.setErrorRateCompliant(errorRate <= sloConfig.getThresholds().getErrorRate());
// Calculate response time percentiles
ValueAtPercentile[] percentiles = responseTimeTimer.takeSnapshot().percentileValues();
double p95ResponseTime = Arrays.stream(percentiles)
.filter(p -> p.percentile() == 0.95)
.mapToDouble(ValueAtPercentile::value)
.findFirst()
.orElse(0.0);
result.setP95ResponseTime(p95ResponseTime);
result.setResponseTimeCompliant(p95ResponseTime <= sloConfig.getThresholds().getResponseTimeP95());
// Calculate availability
double availability = total > 0 ? (total - failed) / total : 1.0;
result.setAvailability(availability);
result.setAvailabilityCompliant(availability >= sloConfig.getThresholds().getAvailability());
// Overall compliance
result.setOverallCompliant(
result.isErrorRateCompliant() &&
result.isResponseTimeCompliant() &&
result.isAvailabilityCompliant()
);
log.debug("SLO Compliance calculated: {}", result.isOverallCompliant());
} catch (Exception e) {
log.error("Failed to calculate SLO compliance", e);
result.setOverallCompliant(false);
result.setErrorMessage(e.getMessage());
}
return result;
}
/**
* Generate SLI (Service Level Indicator) data for Keptn
*/
public List<SLIMetric> generateSLIMetrics() {
SLOComplianceResult compliance = calculateSLOCompliance();
List<SLIMetric> metrics = new ArrayList<>();
metrics.add(SLIMetric.builder()
.metric("error_rate")
.value(compliance.getErrorRate())
.success(compliance.isErrorRateCompliant())
.threshold(sloConfig.getThresholds().getErrorRate())
.build());
metrics.add(SLIMetric.builder()
.metric("response_time_p95")
.value(compliance.getP95ResponseTime())
.success(compliance.isResponseTimeCompliant())
.threshold(sloConfig.getThresholds().getResponseTimeP95())
.build());
metrics.add(SLIMetric.builder()
.metric("availability")
.value(compliance.getAvailability())
.success(compliance.isAvailabilityCompliant())
.threshold(sloConfig.getThresholds().getAvailability())
.build());
return metrics;
}
private void updateAvailabilityGauge() {
double total = totalRequests.count();
double failed = failedRequests.count();
double availability = total > 0 ? (total - failed) / total : 1.0;
availabilityGauge.set(availability);
}
// DTO Classes
@Data
public static class SLOComplianceResult {
private Instant timestamp;
private double errorRate;
private double p95ResponseTime;
private double availability;
private boolean errorRateCompliant;
private boolean responseTimeCompliant;
private boolean availabilityCompliant;
private boolean overallCompliant;
private String errorMessage;
}
@Data
@Builder
public static class SLIMetric {
private String metric;
private double value;
private boolean success;
private double threshold;
private String message;
}
}
Step 5: Quality Gate Service
@Service
@Slf4j
public class QualityGateService {
private final KeptnService keptnService;
private final SLOService sloService;
private final TestResultService testResultService;
private final SecurityScanService securityScanService;
public QualityGateService(KeptnService keptnService,
SLOService sloService,
TestResultService testResultService,
SecurityScanService securityScanService) {
this.keptnService = keptnService;
this.sloService = sloService;
this.testResultService = testResultService;
this.securityScanService = securityScanService;
}
/**
* Execute comprehensive quality gate evaluation
*/
public QualityGateResult executeQualityGate(QualityGateRequest request) {
log.info("Starting quality gate evaluation for project: {}, service: {}, stage: {}",
request.getProject(), request.getService(), request.getStage());
QualityGateResult result = new QualityGateResult();
result.setProject(request.getProject());
result.setService(request.getService());
result.setStage(request.getStage());
result.setStartTime(Instant.now());
try {
// Step 1: Start Keptn evaluation
String evaluationId = startKeptnEvaluation(request);
result.setEvaluationId(evaluationId);
// Step 2: Run performance checks
PerformanceResult performance = evaluatePerformance(request);
result.setPerformanceResult(performance);
// Step 3: Run security checks
SecurityResult security = evaluateSecurity(request);
result.setSecurityResult(security);
// Step 4: Run test validation
TestValidationResult testValidation = validateTests(request);
result.setTestValidationResult(testValidation);
// Step 5: Run business metrics validation
BusinessMetricsResult businessMetrics = validateBusinessMetrics(request);
result.setBusinessMetricsResult(businessMetrics);
// Step 6: Overall assessment
boolean passed = determineOverallResult(result);
result.setPassed(passed);
result.setEndTime(Instant.now());
// Step 7: Send results to Keptn
sendResultsToKeptn(result, evaluationId);
log.info("Quality gate evaluation completed: {}", passed ? "PASSED" : "FAILED");
} catch (Exception e) {
log.error("Quality gate evaluation failed", e);
result.setPassed(false);
result.setErrorMessage(e.getMessage());
result.setEndTime(Instant.now());
}
return result;
}
/**
* Quick quality gate for fast feedback
*/
public QuickQualityGateResult quickQualityGate(String project, String service, String stage) {
QuickQualityGateResult result = new QuickQualityGateResult();
result.setTimestamp(Instant.now());
try {
// Check SLO compliance
SLOService.SLOComplianceResult sloCompliance = sloService.calculateSLOCompliance();
result.setSloCompliant(sloCompliance.isOverallCompliant());
result.setSloMetrics(sloService.generateSLIMetrics());
// Check test results
TestResult testResult = testResultService.getLatestTestResult(project, service, stage);
result.setTestsPassed(testResult != null && testResult.isPassed());
// Quick security check
boolean securityPassed = securityScanService.quickSecurityScan(project, service, stage);
result.setSecurityPassed(securityPassed);
// Overall result
result.setPassed(
result.isSloCompliant() &&
result.isTestsPassed() &&
result.isSecurityPassed()
);
} catch (Exception e) {
log.error("Quick quality gate failed", e);
result.setPassed(false);
result.setErrorMessage(e.getMessage());
}
return result;
}
/**
* Evaluate performance SLOs
*/
private PerformanceResult evaluatePerformance(QualityGateRequest request) {
PerformanceResult result = new PerformanceResult();
try {
SLOService.SLOComplianceResult sloCompliance = sloService.calculateSLOCompliance();
List<SLOService.SLIMetric> sliMetrics = sloService.generateSLIMetrics();
result.setSloCompliant(sloCompliance.isOverallCompliant());
result.setSliMetrics(sliMetrics);
result.setErrorRate(sloCompliance.getErrorRate());
result.setResponseTimeP95(sloCompliance.getP95ResponseTime());
result.setAvailability(sloCompliance.getAvailability());
// Additional performance checks
result.setThroughputAcceptable(checkThroughput());
result.setResourceUsageAcceptable(checkResourceUsage());
} catch (Exception e) {
log.error("Performance evaluation failed", e);
result.setSloCompliant(false);
result.setErrorMessage(e.getMessage());
}
return result;
}
/**
* Evaluate security compliance
*/
private SecurityResult evaluateSecurity(QualityGateRequest request) {
SecurityResult result = new SecurityResult();
try {
SecurityScanResult scanResult = securityScanService.performSecurityScan(
request.getProject(), request.getService(), request.getStage()
);
result.setVulnerabilities(scanResult.getVulnerabilities());
result.setCriticalVulnerabilities(scanResult.getCriticalVulnerabilities());
result.setCompliancePassed(scanResult.isCompliant());
result.setScanScore(scanResult.getScore());
// Security thresholds
result.setPassed(
scanResult.getCriticalVulnerabilities() == 0 &&
scanResult.isCompliant() &&
scanResult.getScore() >= 8.0
);
} catch (Exception e) {
log.error("Security evaluation failed", e);
result.setPassed(false);
result.setErrorMessage(e.getMessage());
}
return result;
}
/**
* Validate test results
*/
private TestValidationResult validateTests(QualityGateRequest request) {
TestValidationResult result = new TestValidationResult();
try {
TestResult testResult = testResultService.getLatestTestResult(
request.getProject(), request.getService(), request.getStage()
);
if (testResult == null) {
result.setPassed(false);
result.setErrorMessage("No test results available");
return result;
}
result.setTotalTests(testResult.getTotalTests());
result.setPassedTests(testResult.getPassedTests());
result.setFailedTests(testResult.getFailedTests());
result.setSkippedTests(testResult.getSkippedTests());
result.setCoveragePercentage(testResult.getCoveragePercentage());
result.setExecutionTime(testResult.getExecutionTime());
// Test quality thresholds
double passRate = (double) testResult.getPassedTests() / testResult.getTotalTests();
result.setPassRate(passRate);
result.setPassed(
passRate >= 0.95 && // 95% pass rate
testResult.getCoveragePercentage() >= 0.80 && // 80% coverage
testResult.getFailedTests() == 0 // No test failures
);
} catch (Exception e) {
log.error("Test validation failed", e);
result.setPassed(false);
result.setErrorMessage(e.getMessage());
}
return result;
}
/**
* Validate business metrics
*/
private BusinessMetricsResult validateBusinessMetrics(QualityGateRequest request) {
BusinessMetricsResult result = new BusinessMetricsResult();
try {
BusinessMetrics metrics = testResultService.getBusinessMetrics(
request.getProject(), request.getService(), request.getStage()
);
result.setConversionRate(metrics.getConversionRate());
result.setRevenueImpact(metrics.getRevenueImpact());
result.setUserSatisfaction(metrics.getUserSatisfaction());
result.setErrorImpact(metrics.getErrorImpact());
// Business thresholds
result.setPassed(
metrics.getConversionRate() >= 0.02 && // 2% conversion rate
metrics.getRevenueImpact() >= 0 && // No negative revenue impact
metrics.getUserSatisfaction() >= 4.0 && // 4+ star satisfaction
metrics.getErrorImpact() <= 0.01 // 1% error impact
);
} catch (Exception e) {
log.error("Business metrics validation failed", e);
result.setPassed(false);
result.setErrorMessage(e.getMessage());
}
return result;
}
private String startKeptnEvaluation(QualityGateRequest request) {
KeptnService.EvaluationStartRequest evaluationRequest =
new KeptnService.EvaluationStartRequest(
request.getProject(),
request.getService(),
request.getStage(),
request.getStartTime(),
request.getEndTime()
);
evaluationRequest.getLabels().put("quality_gate", "true");
evaluationRequest.getLabels().put("version", request.getVersion());
KeptnService.EvaluationStartResponse response =
keptnService.startEvaluation(evaluationRequest);
return response.getKeptnContext();
}
private boolean determineOverallResult(QualityGateResult result) {
return result.getPerformanceResult().isSloCompliant() &&
result.getSecurityResult().isPassed() &&
result.getTestValidationResult().isPassed() &&
result.getBusinessMetricsResult().isPassed();
}
private void sendResultsToKeptn(QualityGateResult result, String evaluationId) {
// Send test results
TestResult testResult = new TestResult();
testResult.setPassed(result.isPassed());
testResult.setTotalTests(1); // Single quality gate test
testResult.setPassedTests(result.isPassed() ? 1 : 0);
testResult.setFailedTests(result.isPassed() ? 0 : 1);
keptnService.sendTestFinished(
result.getProject(),
result.getService(),
result.getStage(),
testResult
);
// Send deployment result
keptnService.sendDeploymentFinished(
result.getProject(),
result.getService(),
result.getStage(),
result.isPassed()
);
}
private boolean checkThroughput() {
// Implement throughput check
return true;
}
private boolean checkResourceUsage() {
// Implement resource usage check
return true;
}
// DTO Classes
@Data
public static class QualityGateRequest {
private String project;
private String service;
private String stage;
private String version;
private LocalDateTime startTime;
private LocalDateTime endTime;
private Map<String, String> labels;
}
@Data
public static class QualityGateResult {
private String project;
private String service;
private String stage;
private String evaluationId;
private Instant startTime;
private Instant endTime;
private boolean passed;
private String errorMessage;
private PerformanceResult performanceResult;
private SecurityResult securityResult;
private TestValidationResult testValidationResult;
private BusinessMetricsResult businessMetricsResult;
}
@Data
public static class PerformanceResult {
private boolean sloCompliant;
private List<SLOService.SLIMetric> sliMetrics;
private double errorRate;
private double responseTimeP95;
private double availability;
private boolean throughputAcceptable;
private boolean resourceUsageAcceptable;
private String errorMessage;
}
@Data
public static class SecurityResult {
private int vulnerabilities;
private int criticalVulnerabilities;
private boolean compliancePassed;
private double scanScore;
private boolean passed;
private String errorMessage;
}
@Data
public static class TestValidationResult {
private int totalTests;
private int passedTests;
private int failedTests;
private int skippedTests;
private double coveragePercentage;
private long executionTime;
private double passRate;
private boolean passed;
private String errorMessage;
}
@Data
public static class BusinessMetricsResult {
private double conversionRate;
private double revenueImpact;
private double userSatisfaction;
private double errorImpact;
private boolean passed;
private String errorMessage;
}
@Data
public static class QuickQualityGateResult {
private Instant timestamp;
private boolean sloCompliant;
private List<SLOService.SLIMetric> sloMetrics;
private boolean testsPassed;
private boolean securityPassed;
private boolean passed;
private String errorMessage;
}
}
Step 6: REST Controllers for Quality Gates
@RestController
@RequestMapping("/api/quality-gates")
@Slf4j
public class QualityGateController {
private final QualityGateService qualityGateService;
private final SLOService sloService;
public QualityGateController(QualityGateService qualityGateService,
SLOService sloService) {
this.qualityGateService = qualityGateService;
this.sloService = sloService;
}
@PostMapping("/evaluate")
public ResponseEntity<QualityGateService.QualityGateResult> evaluateQualityGate(
@RequestBody @Valid QualityGateService.QualityGateRequest request) {
log.info("Received quality gate evaluation request for {}/{}/{}",
request.getProject(), request.getService(), request.getStage());
QualityGateService.QualityGateResult result =
qualityGateService.executeQualityGate(request);
if (result.isPassed()) {
return ResponseEntity.ok(result);
} else {
return ResponseEntity.status(412) // Precondition Failed
.body(result);
}
}
@GetMapping("/quick/{project}/{service}/{stage}")
public ResponseEntity<QualityGateService.QuickQualityGateResult> quickQualityGate(
@PathVariable String project,
@PathVariable String service,
@PathVariable String stage) {
QualityGateService.QuickQualityGateResult result =
qualityGateService.quickQualityGate(project, service, stage);
return ResponseEntity.ok(result);
}
@GetMapping("/slo/compliance")
public ResponseEntity<SLOService.SLOComplianceResult> getSLOCompliance() {
SLOService.SLOComplianceResult compliance = sloService.calculateSLOCompliance();
return ResponseEntity.ok(compliance);
}
@GetMapping("/slo/metrics")
public ResponseEntity<List<SLOService.SLIMetric>> getSLIMetrics() {
List<SLOService.SLIMetric> metrics = sloService.generateSLIMetrics();
return ResponseEntity.ok(metrics);
}
@PostMapping("/deployment/{project}/{service}/{stage}")
public ResponseEntity<Void> reportDeployment(
@PathVariable String project,
@PathVariable String service,
@PathVariable String stage,
@RequestParam String version,
@RequestParam boolean success) {
// This would typically be called by your CI/CD pipeline
log.info("Deployment reported: {}/{}/{} version {} - success: {}",
project, service, stage, version, success);
// Trigger quality gate evaluation if deployment was successful
if (success) {
QualityGateService.QualityGateRequest request = new QualityGateService.QualityGateRequest();
request.setProject(project);
request.setService(service);
request.setStage(stage);
request.setVersion(version);
request.setStartTime(LocalDateTime.now().minusMinutes(30));
request.setEndTime(LocalDateTime.now());
// Async evaluation
CompletableFuture.runAsync(() -> {
qualityGateService.executeQualityGate(request);
});
}
return ResponseEntity.accepted().build();
}
}
Step 7: Metrics Collection Filter
@Component
@Slf4j
public class MetricsFilter implements Filter {
private final SLOService sloService;
public MetricsFilter(SLOService sloService) {
this.sloService = sloService;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
long startTime = System.currentTimeMillis();
boolean success = false;
int statusCode = 200;
try {
chain.doFilter(request, response);
success = true;
if (response instanceof HttpServletResponse) {
statusCode = ((HttpServletResponse) response).getStatus();
}
} catch (Exception e) {
success = false;
statusCode = 500;
throw e;
} finally {
long duration = System.currentTimeMillis() - startTime;
// Record metrics for SLO calculation
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String method = httpRequest.getMethod();
String endpoint = httpRequest.getRequestURI();
sloService.recordHttpRequest(method, endpoint, statusCode, duration, success);
}
}
}
}
Step 8: Configuration Classes
@Configuration
@ConfigurationProperties(prefix = "keptn")
@Validated
@Data
public class KeptnConfig {
@Valid
private Api api = new Api();
@NotBlank
private String project;
@NotBlank
private String service;
@NotBlank
private String stage;
@Data
public static class Api {
@NotBlank
private String url;
private String token;
}
}
@Configuration
@ConfigurationProperties(prefix = "slo")
@Validated
@Data
public class SLOConfig {
@Valid
private Thresholds thresholds = new Thresholds();
@Data
public static class Thresholds {
private int responseTimeP95 = 500;
private double errorRate = 0.05;
private int throughputMin = 10;
private double availability = 0.995;
}
}
Step 9: Keptn Configuration Files
slo.yaml:
--- spec_version: "1.0" comparison: compare_with: "single_result" include_result_with_score: "pass" aggregate_function: avg objectives: - sli: response_time_p95 pass: - criteria: - "<=500" warning: - criteria: - "<=1000" weight: 1 - sli: error_rate pass: - criteria: - "<=0.05" warning: - criteria: - "<=0.1" weight: 1 - sli: availability pass: - criteria: - ">=0.995" warning: - criteria: - ">=0.99" weight: 1 total_score: pass: "90%" warning: "75%"
sli.yaml:
---
spec_version: "1.0"
indicators:
response_time_p95:
query: "histogram_quantile(0.95, sum(rate(http_response_time_seconds_bucket[5m])) by (le))"
error_rate:
query: "sum(rate(http_requests_total{status=~'5..'}[5m])) / sum(rate(http_requests_total[5m]))"
availability:
query: "1 - (sum(rate(http_requests_total{status=~'5..'}[5m])) / sum(rate(http_requests_total[5m])))"
Running the Quality Gate System
- Set up Keptn:
# Install Keptn curl -sL https://get.keptn.sh | bash keptn install --use-case=continuous-delivery # Create project keptn create project ecommerce-platform --shipyard=shipyard.yaml
- Configure environment variables:
export KEPTN_API_URL=http://localhost:8080/api export KEPTN_API_TOKEN=your-keptn-token
- Run the application:
mvn spring-boot:run
- Trigger quality gate:
curl -X POST http://localhost:8080/api/quality-gates/evaluate \
-H "Content-Type: application/json" \
-d '{
"project": "ecommerce-platform",
"service": "order-service",
"stage": "production",
"version": "1.2.3",
"startTime": "2024-01-15T10:00:00",
"endTime": "2024-01-15T11:00:00"
}'
Best Practices
1. SLO Design
- Set realistic targets based on historical data
- Involve stakeholders in SLO definition
- Use error budgets for risk management
- Monitor SLO burn rate for early warnings
2. Quality Gate Strategy
- Start simple with basic metrics
- Gradually add complexity as maturity increases
- Implement circuit breakers for failing quality gates
- Use rolling evaluations for continuous assessment
3. Monitoring and Alerting
- Track quality gate pass/fail rates
- Monitor false positive/negative rates
- Set up alerts for quality gate failures
- Maintain audit trails for compliance
Conclusion
Implementing Keptn quality gates in Java provides:
- Automated quality validation at every deployment
- Data-driven release decisions based on SLOs
- Early problem detection before user impact
- Consistent quality standards across environments
- Comprehensive visibility into release health
By combining Keptn's orchestration capabilities with Java's robust ecosystem, you can build a reliable quality gate system that ensures only high-quality releases reach production while providing teams with the confidence to deploy frequently and safely.