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.