Automated Quality Gates with Keptn in Java: Ensuring Deployment Confidence

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

  1. 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
  1. Configure environment variables:
   export KEPTN_API_URL=http://localhost:8080/api
export KEPTN_API_TOKEN=your-keptn-token
  1. Run the application:
   mvn spring-boot:run
  1. 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.

Leave a Reply

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


Macro Nepal Helper