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