google-site-verification: google61fe8ba583a51912.html
Modern Microservices: Implementing Kuma Service Mesh with Java Applications

Article

Kuma is an open-source, universal service mesh that runs on both Kubernetes and VMs. Built by Kong, Kuma provides a robust control plane that works with Envoy proxy to deliver security, reliability, and observability for microservices. For Java applications, Kuma offers seamless integration with minimal code changes while providing enterprise-grade service mesh capabilities.

In this guide, we'll explore how to integrate Java microservices with Kuma service mesh to achieve traffic management, security, observability, and resilience.

Why Kuma Service Mesh for Java?

  • Universal Deployment: Works across Kubernetes, VMs, and containers
  • Envoy-Powered: Leverages battle-tested Envoy proxy
  • Multi-Zone Support: Native multi-zone and multi-cloud capabilities
  • Zero-Trust Security: Built-in mTLS and service-to-service authentication
  • Comprehensive Observability: Metrics, tracing, and logging out of the box
  • Developer-Friendly: Easy to adopt with minimal application changes

Part 1: Kuma Architecture Overview

1.1 Kuma Components

Java Application → Kuma Data Plane (Envoy) → Kuma Control Plane → Management APIs ↓ ↓ ↓ ↓ Business Logic Traffic Routing Policies Kubernetes Spring Boot Security Configuration Docker Micronaut Observability Service Discovery VM Quarkus

1.2 Project Structure

java-kuma-mesh/ ├── user-service/ │ ├── src/main/java/com/example/user/ │ ├── Dockerfile │ └── kuma/ │ └── mesh.yaml ├── order-service/ │ ├── src/main/java/com/example/order/ │ └── Dockerfile ├── api-gateway/ │ ├── src/main/java/com/example/gateway/ │ └── Dockerfile ├── kuma-config/ │ ├── mesh.yaml │ ├── traffic-route.yaml │ └── circuit-breaker.yaml └── kubernetes/ ├── kuma-install.yaml └── services/ ├── user-service.yaml └── order-service.yaml

Part 2: Dependencies and Setup

2.1 Maven Dependencies

<!-- pom.xml --> <properties> <spring-boot.version>3.1.0</spring-boot.version> <micrometer.version>1.11.5</micrometer.version> <resilience4j.version>2.1.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> <!-- Observability --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> <version>${micrometer.version}</version> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-brave</artifactId> <version>${micrometer.version}</version> </dependency> <!-- Resilience --> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot3</artifactId> <version>${resilience4j.version}</version> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-circuitbreaker</artifactId> <version>${resilience4j.version}</version> </dependency> <!-- OpenTelemetry --> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-api</artifactId> <version>1.31.0</version> </dependency> <!-- HTTP Client --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> <version>${spring-boot.version}</version> </dependency> </dependencies>

Part 3: Java Service Implementation

3.1 User Service with Kuma Integration

// File: user-service/src/main/java/com/example/user/UserServiceApplication.java package com.example.user; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; @SpringBootApplication @EnableConfigurationProperties(UserServiceProperties.class) public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } } // File: user-service/src/main/java/com/example/user/UserServiceProperties.java package com.example.user; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "user.service") public record UserServiceProperties( String name, String version, String environment, MeshProperties mesh ) { public record MeshProperties( boolean enabled, String zone, String serviceAccount, int proxyPort ) {} } // File: user-service/src/main/java/com/example/user/UserController.java package com.example.user; import io.micrometer.core.annotation.Timed; import io.micrometer.observation.annotation.Observed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @RestController @RequestMapping("/api/v1/users") public class UserController { private static final Logger logger = LoggerFactory.getLogger(UserController.class); private final Map<String, User> users = new ConcurrentHashMap<>(); private final UserServiceProperties properties; public UserController(UserServiceProperties properties) { this.properties = properties; // Initialize with sample data users.put("1", new User("1", "[email protected]", "John Doe", "ACTIVE")); users.put("2", new User("2", "[email protected]", "Jane Smith", "ACTIVE")); } @GetMapping @Timed(value = "user.service.get.all", description = "Get all users") @Observed(name = "user.get.all", contextualName = "user-service.get-all-users") public ResponseEntity<List<User>> getAllUsers() { logger.info("Fetching all users - Service: {}, Zone: {}", properties.name(), properties.mesh().zone()); return ResponseEntity.ok(List.copyOf(users.values())); } @GetMapping("/{userId}") @Timed(value = "user.service.get.by.id", description = "Get user by ID") public ResponseEntity<User> getUserById(@PathVariable String userId) { logger.info("Fetching user by ID: {} - Service: {}", userId, properties.name()); User user = users.get(userId); if (user == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(user); } @PostMapping @Timed(value = "user.service.create", description = "Create user") public ResponseEntity<User> createUser(@RequestBody CreateUserRequest request) { logger.info("Creating user: {} - Service: {}", request.email(), properties.name()); String userId = String.valueOf(users.size() + 1); User user = new User(userId, request.email(), request.name(), "ACTIVE"); users.put(userId, user); return ResponseEntity.ok(user); } @GetMapping("/health") public ResponseEntity<Map<String, String>> health() { return ResponseEntity.ok(Map.of( "status", "healthy", "service", properties.name(), "version", properties.version(), "zone", properties.mesh().zone(), "meshEnabled", String.valueOf(properties.mesh().enabled()) )); } @GetMapping("/metrics/info") public ResponseEntity<Map<String, Object>> metricsInfo() { return ResponseEntity.ok(Map.of( "service", properties.name(), "totalUsers", users.size(), "mesh", Map.of( "enabled", properties.mesh().enabled(), "zone", properties.mesh().zone(), "proxyPort", properties.mesh().proxyPort() ) )); } } // File: user-service/src/main/java/com/example/user/User.java package com.example.user; public record User( String id, String email, String name, String status ) {} // File: user-service/src/main/java/com/example/user/CreateUserRequest.java package com.example.user; public record CreateUserRequest( String email, String name ) {}

3.2 Order Service with Resilient Communication

// File: order-service/src/main/java/com/example/order/OrderServiceApplication.java package com.example.order; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableConfigurationProperties(OrderServiceProperties.class) public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } } // File: order-service/src/main/java/com/example/order/OrderController.java package com.example.order; import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; import io.github.resilience4j.retry.annotation.Retry; import io.github.resilience4j.timelimiter.annotation.TimeLimiter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @RestController @RequestMapping("/api/v1/orders") public class OrderController { private static final Logger logger = LoggerFactory.getLogger(OrderController.class); private final Map<String, Order> orders = new ConcurrentHashMap<>(); private final OrderServiceProperties properties; private final WebClient webClient; public OrderController(OrderServiceProperties properties, WebClient.Builder webClientBuilder) { this.properties = properties; this.webClient = webClientBuilder.baseUrl("http://user-service:8080").build(); } @PostMapping @CircuitBreaker(name = "orderService", fallbackMethod = "createOrderFallback") @Retry(name = "orderService") @TimeLimiter(name = "orderService") public Mono<ResponseEntity<Order>> createOrder(@RequestBody CreateOrderRequest request) { logger.info("Creating order for user: {} - Service: {}", request.userId(), properties.name()); // Validate user exists via user service (through Kuma mesh) return validateUser(request.userId()) .flatMap(userValid -> { if (!userValid) { return Mono.just(ResponseEntity.badRequest() .body(new Order("invalid", "USER_NOT_FOUND", null, "FAILED"))); } String orderId = String.valueOf(orders.size() + 1); Order order = new Order(orderId, request.userId(), request.items(), "CREATED"); orders.put(orderId, order); logger.info("Order created successfully: {} - Service: {}", orderId, properties.name()); return Mono.just(ResponseEntity.ok(order)); }); } @GetMapping("/{orderId}") public ResponseEntity<Order> getOrder(@PathVariable String orderId) { logger.info("Fetching order: {} - Service: {}", orderId, properties.name()); Order order = orders.get(orderId); if (order == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(order); } @GetMapping public ResponseEntity<List<Order>> getAllOrders() { logger.info("Fetching all orders - Service: {}", properties.name()); return ResponseEntity.ok(List.copyOf(orders.values())); } @GetMapping("/health") public ResponseEntity<Map<String, String>> health() { return ResponseEntity.ok(Map.of( "status", "healthy", "service", properties.name(), "version", properties.version(), "zone", properties.mesh().zone() )); } private Mono<Boolean> validateUser(String userId) { return webClient.get() .uri("/api/v1/users/{userId}", userId) .retrieve() .toBodilessEntity() .map(response -> response.getStatusCode().is2xxSuccessful()) .doOnError(error -> logger.warn("User validation failed for {}: {}", userId, error.getMessage())) .onErrorReturn(false); } // Fallback method for circuit breaker public Mono<ResponseEntity<Order>> createOrderFallback(CreateOrderRequest request, Exception ex) { logger.warn("Order creation fallback triggered for user: {}, error: {}", request.userId(), ex.getMessage()); Order fallbackOrder = new Order("fallback", request.userId(), request.items(), "PENDING_VALIDATION"); return Mono.just(ResponseEntity.accepted().body(fallbackOrder)); } } // File: order-service/src/main/java/com/example/order/Order.java package com.example.order; import java.util.List; public record Order( String id, String userId, List<OrderItem> items, String status ) {} // File: order-service/src/main/java/com/example/order/CreateOrderRequest.java package com.example.order; import java.util.List; public record CreateOrderRequest( String userId, List<OrderItem> items ) {} // File: order-service/src/main/java/com/example/order/OrderItem.java package com.example.order; public record OrderItem( String productId, int quantity, double price ) {} // File: order-service/src/main/java/com/example/order/OrderServiceProperties.java package com.example.order; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "order.service") public record OrderServiceProperties( String name, String version, String environment, MeshProperties mesh ) { public record MeshProperties( boolean enabled, String zone, String serviceAccount, int proxyPort ) {} }

Part 4: Kuma Configuration

4.1 Mesh Configuration

# kuma-config/mesh.yaml apiVersion: kuma.io/v1alpha1 kind: Mesh metadata: name: default spec: mtls: backends: - name: ca-1 type: builtin enabledBackend: ca-1 logging: backends: - name: file-backend type: file format: '{"start_time": "%START_TIME%", "method": "%REQ(:METHOD)%", "path": "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%", "protocol": "%PROTOCOL%", "response_code": "%RESPONSE_CODE%", "response_flags": "%RESPONSE_FLAGS%", "bytes_received": "%BYTES_RECEIVED%", "bytes_sent": "%BYTES_SENT%", "duration": "%DURATION%", "upstream_service": "%UPSTREAM_CLUSTER%", "upstream_host": "%UPSTREAM_HOST%", "x_forwarded_for": "%REQ(X-FORWARDED-FOR)%", "user_agent": "%REQ(USER-AGENT)%", "request_id": "%REQ(X-REQUEST-ID)%", "authority": "%REQ(:AUTHORITY)%", "upstream_local_address": "%UPSTREAM_LOCAL_ADDRESS%"}' file: path: /tmp/access.log tracing: backends: - name: jaeger-collector type: jaeger sampling: 100.0 conf: collectorEndpoint: http://jaeger-collector:14268/api/traces metrics: enabledBackend: prometheus-1 backends: - name: prometheus-1 type: prometheus conf: port: 5670 path: /metrics tags: kuma.io/service: dataplane_metrics

4.2 Traffic Route Configuration

# kuma-config/traffic-route.yaml apiVersion: kuma.io/v1alpha1 kind: TrafficRoute mesh: default metadata: name: route-all-traffic spec: sources: - match: kuma.io/service: '*' destinations: - match: kuma.io/service: '*' conf: http: - match: path: prefix: / destination: kuma.io/service: '*' --- apiVersion: kuma.io/v1alpha1 kind: TrafficRoute mesh: default metadata: name: canary-user-service spec: sources: - match: kuma.io/service: 'order-service' destinations: - match: kuma.io/service: 'user-service' conf: http: - match: headers: x-canary: 'true' split: - weight: 10 destination: kuma.io/service: 'user-service-v2' - weight: 90 destination: kuma.io/service: 'user-service' - match: path: prefix: '/api/v1/users' split: - weight: 100 destination: kuma.io/service: 'user-service'

4.3 Circuit Breaker Configuration

# kuma-config/circuit-breaker.yaml apiVersion: kuma.io/v1alpha1 kind: CircuitBreaker mesh: default metadata: name: order-service-cb spec: sources: - match: kuma.io/service: 'order-service' destinations: - match: kuma.io/service: 'user-service' conf: interval: 30s baseEjectionTime: 30s maxEjectionPercent: 50 thresholds: maxConnections: 100 maxRequests: 100 maxRetries: 3 maxPendingRequests: 50 detectors: totalErrors: consecutive: 5 gatewayErrors: consecutive: 10 localOriginErrors: consecutive: 7

4.4 Retry Policy

# kuma-config/retry-policy.yaml apiVersion: kuma.io/v1alpha1 kind: RetryPolicy mesh: default metadata: name: order-service-retries spec: sources: - match: kuma.io/service: 'order-service' destinations: - match: kuma.io/service: 'user-service' conf: http: numRetries: 3 perTryTimeout: 2s backOff: baseInterval: 0.1s maxInterval: 3s retryOn: - gateway-error - connect-failure - refused-stream - retriable-4xx - reset

4.5 Fault Injection for Testing

# kuma-config/fault-injection.yaml apiVersion: kuma.io/v1alpha1 kind: FaultInjection mesh: default metadata: name: user-service-delay spec: sources: - match: kuma.io/service: 'test-client' destinations: - match: kuma.io/service: 'user-service' conf: delay: percentage: 50 value: 5s abort: percentage: 10 httpStatus: 503

Part 5: Kubernetes Deployment

5.1 Kuma Installation

# kubernetes/kuma-install.yaml apiVersion: v1 kind: Namespace metadata: name: kuma-system --- apiVersion: apps/v1 kind: Deployment metadata: namespace: kuma-system name: kuma-control-plane labels: app: kuma-control-plane spec: replicas: 1 selector: matchLabels: app: kuma-control-plane template: metadata: labels: app: kuma-control-plane annotations: kuma.io/mesh: default spec: containers: - name: kuma-cp image: kong-docker-kuma-docker.bintray.io/kuma-cp:2.6.0 ports: - containerPort: 5681 - containerPort: 5682 env: - name: KUMA_MULTI_ZONE_GLOBAL_KDS_GRPC_PORT value: "5685" resources: requests: memory: "256Mi" cpu: "100m" limits: memory: "512Mi" cpu: "500m" --- apiVersion: v1 kind: Service metadata: namespace: kuma-system name: kuma-control-plane labels: app: kuma-control-plane spec: ports: - port: 5681 targetPort: 5681 name: grpc - port: 5682 targetPort: 5682 name: http selector: app: kuma-control-plane

5.2 User Service Deployment

# kubernetes/services/user-service.yaml apiVersion: apps/v1 kind: Deployment metadata: name: user-service namespace: default labels: app: user-service version: v1 spec: replicas: 3 selector: matchLabels: app: user-service version: v1 template: metadata: labels: app: user-service version: v1 annotations: kuma.io/mesh: default kuma.io/sidecar-injection: "enabled" prometheus.io/scrape: "true" prometheus.io/port: "5670" prometheus.io/path: "/metrics" spec: serviceAccountName: user-service containers: - name: user-service image: my-registry/user-service:1.0.0 ports: - containerPort: 8080 env: - name: SPRING_APPLICATION_NAME value: "user-service" - name: USER_SERVICE_NAME value: "user-service" - name: USER_SERVICE_VERSION value: "1.0.0" - name: USER_SERVICE_ENVIRONMENT value: "production" - name: USER_SERVICE_MESH_ENABLED value: "true" - name: USER_SERVICE_MESH_ZONE value: "us-west-2" resources: requests: memory: "256Mi" cpu: "100m" limits: memory: "512Mi" cpu: "300m" livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 5 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: user-service namespace: default labels: app: user-service spec: ports: - port: 80 targetPort: 8080 protocol: TCP name: http selector: app: user-service type: ClusterIP --- apiVersion: v1 kind: ServiceAccount metadata: name: user-service namespace: default

5.3 Order Service Deployment

# kubernetes/services/order-service.yaml apiVersion: apps/v1 kind: Deployment metadata: name: order-service namespace: default labels: app: order-service version: v1 spec: replicas: 2 selector: matchLabels: app: order-service version: v1 template: metadata: labels: app: order-service version: v1 annotations: kuma.io/mesh: default kuma.io/sidecar-injection: "enabled" prometheus.io/scrape: "true" prometheus.io/port: "5670" spec: serviceAccountName: order-service containers: - name: order-service image: my-registry/order-service:1.0.0 ports: - containerPort: 8080 env: - name: SPRING_APPLICATION_NAME value: "order-service" - name: ORDER_SERVICE_NAME value: "order-service" - name: ORDER_SERVICE_VERSION value: "1.0.0" - name: ORDER_SERVICE_ENVIRONMENT value: "production" - name: ORDER_SERVICE_MESH_ENABLED value: "true" - name: ORDER_SERVICE_MESH_ZONE value: "us-west-2" resources: requests: memory: "256Mi" cpu: "100m" limits: memory: "512Mi" cpu: "300m" livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 5 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: order-service namespace: default labels: app: order-service spec: ports: - port: 80 targetPort: 8080 protocol: TCP name: http selector: app: order-service type: ClusterIP --- apiVersion: v1 kind: ServiceAccount metadata: name: order-service namespace: default

Part 6: Application Configuration

6.1 User Service Configuration

# user-service/src/main/resources/application.yaml spring: application: name: user-service server: port: 8080 user: service: name: user-service version: 1.0.0 environment: ${ENVIRONMENT:development} mesh: enabled: ${MESH_ENABLED:true} zone: ${MESH_ZONE:default} service-account: user-service proxy-port: 5670 management: endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: always metrics: enabled: true metrics: export: prometheus: enabled: true tags: application: ${spring.application.name} zone: ${user.service.mesh.zone} environment: ${user.service.environment} logging: level: com.example.user: DEBUG org.springframework.web: INFO io.kuma: DEBUG resilience4j: circuitbreaker: instances: userService: register-health-indicator: true sliding-window-size: 10 minimum-number-of-calls: 5 permitted-number-of-calls-in-half-open-state: 3 automatic-transition-from-open-to-half-open-enabled: true wait-duration-in-open-state: 10s failure-rate-threshold: 50 event-consumer-buffer-size: 10 retry: instances: userService: max-attempts: 3 wait-duration: 2s

6.2 Order Service Configuration

# order-service/src/main/resources/application.yaml spring: application: name: order-service server: port: 8080 order: service: name: order-service version: 1.0.0 environment: ${ENVIRONMENT:development} mesh: enabled: ${MESH_ENABLED:true} zone: ${MESH_ZONE:default} service-account: order-service proxy-port: 5670 management: endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: always metrics: enabled: true metrics: export: prometheus: enabled: true tags: application: ${spring.application.name} zone: ${order.service.mesh.zone} environment: ${order.service.environment} logging: level: com.example.order: DEBUG org.springframework.web: INFO io.kuma: DEBUG resilience4j: circuitbreaker: instances: orderService: register-health-indicator: true sliding-window-size: 10 minimum-number-of-calls: 5 permitted-number-of-calls-in-half-open-state: 3 automatic-transition-from-open-to-half-open-enabled: true wait-duration-in-open-state: 10s failure-rate-threshold: 50 retry: instances: orderService: max-attempts: 3 wait-duration: 1s webclient: timeout: connect: 5s read: 10s write: 10s

Part 7: Observability and Monitoring

7.1 Custom Metrics and Health Checks

// File: user-service/src/main/java/com/example/user/UserServiceMetrics.java package com.example.user; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import org.springframework.stereotype.Component; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @Component public class UserServiceMetrics { private final MeterRegistry meterRegistry; private final Counter userCreationCounter; private final Counter userQueryCounter; private final Timer userQueryTimer; private final ConcurrentHashMap<String, Counter> errorCounters; public UserServiceMetrics(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; this.errorCounters = new ConcurrentHashMap<>(); this.userCreationCounter = Counter.builder("user.service.creation.total") .description("Total number of user creations") .tag("service", "user-service") .register(meterRegistry); this.userQueryCounter = Counter.builder("user.service.query.total") .description("Total number of user queries") .tag("service", "user-service") .register(meterRegistry); this.userQueryTimer = Timer.builder("user.service.query.duration") .description("Time taken for user queries") .tag("service", "user-service") .register(meterRegistry); } public void recordUserCreation() { userCreationCounter.increment(); } public void recordUserQuery() { userQueryCounter.increment(); } public Timer.Sample startQueryTimer() { return Timer.start(meterRegistry); } public void stopQueryTimer(Timer.Sample sample) { sample.stop(userQueryTimer); } public void recordError(String errorType) { Counter errorCounter = errorCounters.computeIfAbsent(errorType, key -> Counter.builder("user.service.errors.total") .description("Total number of errors by type") .tag("service", "user-service") .tag("error_type", errorType) .register(meterRegistry)); errorCounter.increment(); } public void recordMeshRequest(String destination, String status) { Counter.builder("user.service.mesh.requests.total") .description("Requests through service mesh") .tag("service", "user-service") .tag("destination", destination) .tag("status", status) .register(meterRegistry) .increment(); } }

7.2 Enhanced Health Indicator

// File: user-service/src/main/java/com/example/user/UserServiceHealthIndicator.java package com.example.user; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; import java.net.InetAddress; import java.util.HashMap; import java.util.Map; @Component public class UserServiceHealthIndicator implements HealthIndicator { private final UserServiceProperties properties; public UserServiceHealthIndicator(UserServiceProperties properties) { this.properties = properties; } @Override public Health health() { try { Map<String, Object> details = new HashMap<>(); details.put("service", properties.name()); details.put("version", properties.version()); details.put("environment", properties.environment()); details.put("meshEnabled", properties.mesh().enabled()); details.put("meshZone", properties.mesh().zone()); details.put("hostname", InetAddress.getLocalHost().getHostName()); details.put("timestamp", System.currentTimeMillis()); // Add mesh-specific health checks if (properties.mesh().enabled()) { details.put("meshStatus", "connected"); details.put("proxyPort", properties.mesh().proxyPort()); } return Health.up() .withDetails(details) .build(); } catch (Exception e) { return Health.down() .withDetail("error", e.getMessage()) .build(); } } }

Part 8: Testing with Kuma

8.1 Integration Tests

// File: order-service/src/test/java/com/example/order/OrderServiceIntegrationTest.java package com.example.order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ActiveProfiles; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") class OrderServiceIntegrationTest { @LocalServerPort private int port; @Autowired private TestRestTemplate restTemplate; @Test void shouldCreateOrderThroughMesh() { // Given String baseUrl = "http://localhost:" + port; CreateOrderRequest request = new CreateOrderRequest( "1", List.of(new OrderItem("product-1", 2, 29.99)) ); // When ResponseEntity<Order> response = restTemplate.postForEntity( baseUrl + "/api/v1/orders", request, Order.class); // Then assertTrue(response.getStatusCode().is2xxSuccessful()); assertNotNull(response.getBody()); assertEquals("1", response.getBody().userId()); assertNotNull(response.getBody().id()); } @Test void shouldReturnHealthStatusWithMeshInfo() { // Given String baseUrl = "http://localhost:" + port; // When ResponseEntity<String> response = restTemplate.getForEntity( baseUrl + "/api/v1/orders/health", String.class); // Then assertTrue(response.getStatusCode().is2xxSuccessful()); assertTrue(response.getBody().contains("healthy")); assertTrue(response.getBody().contains("order-service")); } }

8.2 Resilience Testing

// File: order-service/src/test/java/com/example/order/OrderServiceResilienceTest.java package com.example.order; import io.github.resilience4j.circuitbreaker.CircuitBreaker; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest @ActiveProfiles("test") class OrderServiceResilienceTest { @Autowired private CircuitBreakerRegistry circuitBreakerRegistry; @Test void shouldConfigureCircuitBreaker() { // When CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("orderService"); // Then assertNotNull(circuitBreaker); assertEquals("orderService", circuitBreaker.getName()); assertEquals(CircuitBreaker.State.CLOSED, circuitBreaker.getState()); } }

Best Practices for Kuma with Java

  1. Gradual Adoption: Start with observability, then add security and traffic management
  2. Proper Tagging: Use consistent tags for services across the mesh
  3. Health Checks: Implement comprehensive health checks for both application and mesh
  4. Circuit Breaking: Combine application-level and mesh-level circuit breaking
  5. Observability: Leverage Kuma's built-in metrics, tracing, and logging
  6. Security: Enable mTLS for service-to-service communication
  7. Testing: Test both with and without mesh in your CI/CD pipeline
  8. Monitoring: Set up alerts for mesh-level and application-level issues

Conclusion

Integrating Kuma Service Mesh with Java applications provides a powerful foundation for building resilient, observable, and secure microservices architectures. By leveraging Kuma's capabilities alongside Java frameworks like Spring Boot, you can:

  • Automate traffic management with sophisticated routing and load balancing
  • Implement zero-trust security with automatic mTLS and service identity
  • Gain comprehensive observability with built-in metrics, tracing, and logging
  • Enhance resilience with circuit breaking, retries, and fault injection
  • Simplify operations with declarative configuration and multi-zone support

The patterns and examples in this guide demonstrate how to effectively integrate Java microservices with Kuma service mesh, creating a robust platform for modern cloud-native applications that can scale across multiple environments and deployment scenarios.

Leave a Reply

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


Macro Nepal Helper