Consul Connect in Java: Comprehensive Service Mesh Integration

Consul Connect provides service-to-service connection authorization and encryption using mutual TLS. It enables secure service communication without modifying application code through a sidecar proxy model.


Core Concepts

What is Consul Connect?

  • Service mesh solution for secure service communication
  • Provides automatic mTLS encryption between services
  • Service discovery and health checking
  • Identity-based authorization policies

Key Components:

  • Service Registration: Services register with Consul
  • Sidecar Proxies: Envoy proxies handle mTLS and traffic routing
  • Intentions: Authorization rules defining service communication
  • mTLS: Automatic certificate rotation and encryption

Architecture Overview

Java App → Envoy Sidecar → Consul → Envoy Sidecar → Java App
↓                              ↓
Service A                      Service B

Dependencies and Setup

Maven Dependencies
<properties>
<spring-boot.version>3.1.0</spring-boot.version>
<consul-api.version>1.4.5</consul-api.version>
<spring-cloud.version>2022.0.3</spring-cloud.version>
</properties>
<dependencies>
<!-- Spring Cloud Consul -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-all</artifactId>
<version>${spring-cloud.version}</version>
</dependency>
<!-- Consul API Client -->
<dependency>
<groupId>com.ecwid.consul</groupId>
<artifactId>consul-api</artifactId>
<version>${consul-api.version}</version>
</dependency>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Resilience4j for circuit breaker -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
<version>${spring-cloud.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Docker Compose for Local Development
# docker-compose.yml
version: '3.8'
services:
consul-server:
image: consul:1.15
container_name: consul-server
ports:
- "8500:8500"
- "8600:8600/udp"
command: >
agent -server -ui -node=consul-server-1
-bootstrap-expect=1 -client=0.0.0.0
environment:
- CONSUL_BIND_INTERFACE=eth0
user-service:
build: ./user-service
container_name: user-service
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=docker
- CONSUL_HOST=consul-server
depends_on:
- consul-server
order-service:
build: ./order-service
container_name: order-service
ports:
- "8081:8081"
environment:
- SPRING_PROFILES_ACTIVE=docker
- CONSUL_HOST=consul-server
depends_on:
- consul-server
# Connect-enabled services with sidecars
user-service-connect:
image: user-service:latest
container_name: user-service-connect
environment:
- SPRING_PROFILES_ACTIVE=connect
- CONSUL_HOST=consul-server
networks:
- consul-mesh
order-service-connect:
image: order-service:latest
container_name: order-service-connect
environment:
- SPRING_PROFILES_ACTIVE=connect
- CONSUL_HOST=consul-server
networks:
- consul-mesh
networks:
consul-mesh:
driver: bridge

Spring Boot Configuration

1. Application Properties
# application.yml
spring:
application:
name: user-service
cloud:
consul:
host: localhost
port: 8500
discovery:
instanceId: ${spring.application.name}:${random.value}
healthCheckPath: /actuator/health
healthCheckInterval: 15s
tags:
- version=1.0.0
- environment=development
config:
enabled: true
format: YAML
data-key: config
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
server:
port: 8080
app:
services:
order-service: order-service
2. Connect-Specific Configuration
# application-connect.yml
spring:
cloud:
consul:
discovery:
# Use Connect for service discovery
use-connect: true
config:
# Enable Connect for config
use-connect: true
app:
connect:
enabled: true
# Sidecar port for service-to-service communication
sidecar-port: 21000
3. Service Registration Configuration
@Configuration
public class ConsulConfig {
@Value("${app.connect.enabled:false}")
private boolean connectEnabled;
@Bean
public ConsulServiceRegistryCustomizer consulServiceRegistryCustomizer() {
return registry -> {
// Add Connect-specific metadata if enabled
if (connectEnabled) {
Map<String, String> metadata = new HashMap<>();
metadata.put("connect-enabled", "true");
metadata.put("sidecar-port", "21000");
// Custom metadata for service mesh
}
};
}
}

Service Implementation

1. Service Registration and Health Checks
@Service
public class ConsulRegistrationService {
private static final Logger logger = LoggerFactory.getLogger(ConsulRegistrationService.class);
private final ConsulClient consulClient;
private final String serviceName;
private final String serviceId;
public ConsulRegistrationService(ConsulClient consulClient, 
@Value("${spring.application.name}") String serviceName) {
this.consulClient = consulClient;
this.serviceName = serviceName;
this.serviceId = serviceName + "-" + UUID.randomUUID().toString().substring(0, 8);
}
@PostConstruct
public void registerService() {
try {
NewService newService = new NewService();
newService.setId(serviceId);
newService.setName(serviceName);
newService.setAddress("localhost");
newService.setPort(8080);
// Health check
NewService.Check serviceCheck = new NewService.Check();
serviceCheck.setHttp("http://localhost:8080/actuator/health");
serviceCheck.setInterval("15s");
serviceCheck.setTimeout("5s");
newService.setCheck(serviceCheck);
// Register service
consulClient.agentServiceRegister(newService);
logger.info("Service registered with Consul: {} [ID: {}]", serviceName, serviceId);
} catch (Exception e) {
logger.error("Failed to register service with Consul", e);
}
}
@PreDestroy
public void deregisterService() {
try {
consulClient.agentServiceDeregister(serviceId);
logger.info("Service deregistered from Consul: {}", serviceId);
} catch (Exception e) {
logger.error("Failed to deregister service from Consul", e);
}
}
public List<ServiceHealth> getHealthyInstances(String serviceName) {
try {
Response<List<ServiceHealth>> response = consulClient.getHealthServices(
serviceName, true, QueryParams.DEFAULT);
return response.getValue();
} catch (Exception e) {
logger.error("Failed to get healthy instances for service: {}", serviceName, e);
return Collections.emptyList();
}
}
}
2. Service Discovery with Connect
@Service
public class ConnectAwareServiceDiscovery {
private static final Logger logger = LoggerFactory.getLogger(ConnectAwareServiceDiscovery.class);
private final ConsulClient consulClient;
private final LoadBalancerClient loadBalancer;
private final boolean connectEnabled;
public ConnectAwareServiceDiscovery(ConsulClient consulClient,
LoadBalancerClient loadBalancer,
@Value("${app.connect.enabled:false}") boolean connectEnabled) {
this.consulClient = consulClient;
this.loadBalancer = loadBalancer;
this.connectEnabled = connectEnabled;
}
public ServiceInstance getServiceInstance(String serviceName) {
if (connectEnabled) {
return getConnectServiceInstance(serviceName);
} else {
return loadBalancer.choose(serviceName);
}
}
private ServiceInstance getConnectServiceInstance(String serviceName) {
try {
List<ServiceHealth> healthyServices = consulClient.getHealthServices(
serviceName, true, QueryParams.DEFAULT).getValue();
if (healthyServices.isEmpty()) {
throw new IllegalStateException("No healthy instances found for service: " + serviceName);
}
// For Connect, we use the sidecar port (typically service port + 1)
ServiceHealth serviceHealth = healthyServices.get(0);
Service service = serviceHealth.getService();
String host = service.getAddress();
int port = getConnectPort(service);
logger.debug("Using Connect-enabled service instance: {}:{}", host, port);
return new DefaultServiceInstance(
service.getId(),
serviceName,
host,
port,
connectEnabled
);
} catch (Exception e) {
logger.error("Failed to discover Connect service: {}", serviceName, e);
throw new ServiceDiscoveryException("Service discovery failed", e);
}
}
private int getConnectPort(Service service) {
// Check for Connect metadata or use default sidecar port
Map<String, String> meta = service.getMeta();
if (meta != null && meta.containsKey("sidecar-port")) {
return Integer.parseInt(meta.get("sidecar-port"));
}
// Default sidecar port calculation
return service.getPort() + 10000;
}
public List<ServiceInstance> getAllServiceInstances(String serviceName) {
try {
List<ServiceHealth> healthyServices = consulClient.getHealthServices(
serviceName, true, QueryParams.DEFAULT).getValue();
return healthyServices.stream()
.map(serviceHealth -> {
Service service = serviceHealth.getService();
int port = connectEnabled ? getConnectPort(service) : service.getPort();
return new DefaultServiceInstance(
service.getId(),
serviceName,
service.getAddress(),
port,
connectEnabled
);
})
.collect(Collectors.toList());
} catch (Exception e) {
logger.error("Failed to get service instances: {}", serviceName, e);
return Collections.emptyList();
}
}
}
3. REST Client with Connect Support
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ConnectAwareServiceDiscovery serviceDiscovery) {
RestTemplate restTemplate = new RestTemplate();
// Add service discovery interceptor
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(new ConnectAwareInterceptor(serviceDiscovery));
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
@Component
public class ConnectAwareInterceptor implements ClientHttpRequestInterceptor {
private static final Logger logger = LoggerFactory.getLogger(ConnectAwareInterceptor.class);
private final ConnectAwareServiceDiscovery serviceDiscovery;
public ConnectAwareInterceptor(ConnectAwareServiceDiscovery serviceDiscovery) {
this.serviceDiscovery = serviceDiscovery;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, 
ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String host = originalUri.getHost();
// Check if this is a service name (not a direct host)
if (isServiceName(host)) {
ServiceInstance instance = serviceDiscovery.getServiceInstance(host);
if (instance != null) {
// Rewrite the URL to use the discovered instance
URI newUri = rewriteUri(originalUri, instance);
logger.debug("Rewriting URI: {} -> {}", originalUri, newUri);
// Create new request with rewritten URI
HttpRequest newRequest = new ServiceHttpRequest(request, newUri);
return execution.execute(newRequest, body);
}
}
return execution.execute(request, body);
}
private boolean isServiceName(String host) {
// Simple heuristic: if it doesn't contain dots and isn't localhost
return host != null && !host.contains(".") && !host.equals("localhost");
}
private URI rewriteUri(URI originalUri, ServiceInstance instance) {
try {
return new URI(
originalUri.getScheme(),
originalUri.getUserInfo(),
instance.getHost(),
instance.getPort(),
originalUri.getPath(),
originalUri.getQuery(),
originalUri.getFragment()
);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid URI", e);
}
}
private static class ServiceHttpRequest implements HttpRequest {
private final HttpRequest delegate;
private final URI uri;
public ServiceHttpRequest(HttpRequest delegate, URI uri) {
this.delegate = delegate;
this.uri = uri;
}
@Override
public String getMethodValue() {
return delegate.getMethodValue();
}
@Override
public URI getURI() {
return uri;
}
@Override
public HttpHeaders getHeaders() {
return delegate.getHeaders();
}
}
}
4. Service-to-Service Communication
@Service
public class OrderServiceClient {
private static final Logger logger = LoggerFactory.getLogger(OrderServiceClient.class);
private final RestTemplate restTemplate;
private final CircuitBreaker circuitBreaker;
private final String orderServiceName;
public OrderServiceClient(RestTemplate restTemplate,
CircuitBreakerRegistry circuitBreakerRegistry,
@Value("${app.services.order-service}") String orderServiceName) {
this.restTemplate = restTemplate;
this.circuitBreaker = circuitBreakerRegistry.circuitBreaker("orderService");
this.orderServiceName = orderServiceName;
}
public Order getOrder(String orderId) {
return circuitBreaker.executeSupplier(() -> {
try {
String url = String.format("http://%s/api/orders/%s", orderServiceName, orderId);
ResponseEntity<Order> response = restTemplate.getForEntity(url, Order.class);
if (response.getStatusCode().is2xxSuccessful()) {
return response.getBody();
} else {
throw new ServiceCallException("Failed to get order: " + response.getStatusCode());
}
} catch (ResourceAccessException e) {
logger.error("Connection failed to order service", e);
throw new ServiceCallException("Order service unavailable", e);
}
});
}
public Order createOrder(OrderRequest orderRequest) {
return circuitBreaker.executeSupplier(() -> {
try {
String url = String.format("http://%s/api/orders", orderServiceName);
ResponseEntity<Order> response = restTemplate.postForEntity(
url, orderRequest, Order.class);
if (response.getStatusCode().is2xxSuccessful()) {
return response.getBody();
} else {
throw new ServiceCallException("Failed to create order: " + response.getStatusCode());
}
} catch (ResourceAccessException e) {
logger.error("Connection failed to order service", e);
throw new ServiceCallException("Order service unavailable", e);
}
});
}
public List<Order> getUserOrders(String userId) {
return circuitBreaker.executeSupplier(() -> {
try {
String url = String.format("http://%s/api/orders/user/%s", orderServiceName, userId);
ParameterizedTypeReference<List<Order>> typeRef = new ParameterizedTypeReference<>() {};
ResponseEntity<List<Order>> response = restTemplate.exchange(
url, HttpMethod.GET, null, typeRef);
if (response.getStatusCode().is2xxSuccessful()) {
return response.getBody();
} else {
throw new ServiceCallException("Failed to get user orders: " + response.getStatusCode());
}
} catch (ResourceAccessException e) {
logger.error("Connection failed to order service", e);
throw new ServiceCallException("Order service unavailable", e);
}
});
}
}
5. User Service Implementation
@RestController
@RequestMapping("/api/users")
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
private final UserService userService;
private final OrderServiceClient orderServiceClient;
public UserController(UserService userService, OrderServiceClient orderServiceClient) {
this.userService = userService;
this.orderServiceClient = orderServiceClient;
}
@GetMapping("/{userId}")
public ResponseEntity<User> getUser(@PathVariable String userId) {
try {
User user = userService.getUserById(userId);
return ResponseEntity.ok(user);
} catch (UserNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
@GetMapping("/{userId}/orders")
public ResponseEntity<UserWithOrders> getUserWithOrders(@PathVariable String userId) {
try {
User user = userService.getUserById(userId);
// Call order service through Connect
List<Order> orders = orderServiceClient.getUserOrders(userId);
UserWithOrders response = new UserWithOrders(user, orders);
return ResponseEntity.ok(response);
} catch (UserNotFoundException e) {
return ResponseEntity.notFound().build();
} catch (ServiceCallException e) {
logger.error("Failed to get orders for user: {}", userId, e);
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();
}
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody UserCreateRequest request) {
User user = userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}
}
@Service
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
private final Map<String, User> userStore = new ConcurrentHashMap<>();
public User getUserById(String userId) {
User user = userStore.get(userId);
if (user == null) {
throw new UserNotFoundException("User not found: " + userId);
}
return user;
}
public User createUser(UserCreateRequest request) {
String userId = UUID.randomUUID().toString();
User user = new User(userId, request.getName(), request.getEmail());
userStore.put(userId, user);
logger.info("Created user: {} [ID: {}]", user.getName(), userId);
return user;
}
}
6. Order Service Implementation
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private static final Logger logger = LoggerFactory.getLogger(OrderController.class);
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/{orderId}")
public ResponseEntity<Order> getOrder(@PathVariable String orderId) {
try {
Order order = orderService.getOrder(orderId);
return ResponseEntity.ok(order);
} catch (OrderNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
@GetMapping("/user/{userId}")
public ResponseEntity<List<Order>> getUserOrders(@PathVariable String userId) {
List<Order> orders = orderService.getOrdersByUser(userId);
return ResponseEntity.ok(orders);
}
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
Order order = orderService.createOrder(request);
return ResponseEntity.status(HttpStatus.CREATED).body(order);
}
}
@Service
public class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
private final Map<String, Order> orderStore = new ConcurrentHashMap<>();
private final Map<String, List<String>> userOrders = new ConcurrentHashMap<>();
public Order getOrder(String orderId) {
Order order = orderStore.get(orderId);
if (order == null) {
throw new OrderNotFoundException("Order not found: " + orderId);
}
return order;
}
public List<Order> getOrdersByUser(String userId) {
List<String> orderIds = userOrders.getOrDefault(userId, Collections.emptyList());
return orderIds.stream()
.map(orderStore::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
public Order createOrder(OrderRequest request) {
String orderId = UUID.randomUUID().toString();
Order order = new Order(orderId, request.getUserId(), request.getItems(), 
request.getTotalAmount(), OrderStatus.CREATED);
orderStore.put(orderId, order);
userOrders.computeIfAbsent(request.getUserId(), k -> new ArrayList<>()).add(orderId);
logger.info("Created order: {} for user: {}", orderId, request.getUserId());
return order;
}
}

Connect Intentions Management

1. Intentions Service
@Service
public class ConsulIntentionsService {
private static final Logger logger = LoggerFactory.getLogger(ConsulIntentionsService.class);
private final ConsulClient consulClient;
public ConsulIntentionsService(ConsulClient consulClient) {
this.consulClient = consulClient;
}
public void allowServiceCommunication(String sourceService, String destinationService) {
try {
// Create intention allowing communication
String intentionId = String.format("%s-to-%s", sourceService, destinationService);
Intention intention = new Intention();
intention.setSourceName(sourceService);
intention.setDestinationName(destinationService);
intention.setAction("allow");
intention.setDescription(String.format("Allow %s to communicate with %s", 
sourceService, destinationService));
// Note: This requires Consul 1.9+ and appropriate permissions
logger.info("Creating intention: {} -> {}", sourceService, destinationService);
} catch (Exception e) {
logger.error("Failed to create intention: {} -> {}", sourceService, destinationService, e);
}
}
public void denyServiceCommunication(String sourceService, String destinationService) {
try {
// Create intention denying communication
String intentionId = String.format("%s-to-%s", sourceService, destinationService);
Intention intention = new Intention();
intention.setSourceName(sourceService);
intention.setDestinationName(destinationService);
intention.setAction("deny");
intention.setDescription(String.format("Deny %s from communicating with %s", 
sourceService, destinationService));
logger.info("Creating deny intention: {} -> {}", sourceService, destinationService);
} catch (Exception e) {
logger.error("Failed to create deny intention: {} -> {}", sourceService, destinationService, e);
}
}
public List<Intention> getIntentionsForService(String serviceName) {
try {
// Get intentions where this service is source or destination
Response<List<Intention>> sourceIntentions = consulClient.getIntentionsForSource(serviceName);
Response<List<Intention>> destinationIntentions = consulClient.getIntentionsForDestination(serviceName);
List<Intention> allIntentions = new ArrayList<>();
allIntentions.addAll(sourceIntentions.getValue());
allIntentions.addAll(destinationIntentions.getValue());
return allIntentions;
} catch (Exception e) {
logger.error("Failed to get intentions for service: {}", serviceName, e);
return Collections.emptyList();
}
}
}
2. Security Configuration
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/actuator/health").permitAll()
.requestMatchers("/actuator/info").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
}

Kubernetes Deployment with Connect

1. User Service Deployment
# k8s/user-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
labels:
app: user-service
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
annotations:
consul.hashicorp.com/connect-inject: "true"
consul.hashicorp.com/connect-service-port: "8080"
consul.hashicorp.com/connect-service-upstreams: "order-service:21001"
spec:
containers:
- name: user-service
image: user-service:latest
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "kubernetes,connect"
- name: CONSUL_HOST
value: "consul-server"
- name: APP_CONNECT_ENABLED
value: "true"
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
name: user-service
labels:
app: user-service
spec:
selector:
app: user-service
ports:
- port: 8080
targetPort: 8080
2. Order Service Deployment
# k8s/order-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
labels:
app: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
annotations:
consul.hashicorp.com/connect-inject: "true"
consul.hashicorp.com/connect-service-port: "8081"
spec:
containers:
- name: order-service
image: order-service:latest
ports:
- containerPort: 8081
env:
- name: SPRING_PROFILES_ACTIVE
value: "kubernetes,connect"
- name: CONSUL_HOST
value: "consul-server"
- name: APP_CONNECT_ENABLED
value: "true"
---
apiVersion: v1
kind: Service
metadata:
name: order-service
labels:
app: order-service
spec:
selector:
app: order-service
ports:
- port: 8081
targetPort: 8081
3. Consul Helm Values
# consul-values.yaml
global:
name: consul
datacenter: dc1
tls:
enabled: true
server:
replicas: 3
bootstrapExpect: 3
securityContext:
runAsNonRoot: false
runAsUser: 0
ui:
enabled: true
service:
type: 'LoadBalancer'
connectInject:
enabled: true
default: true
controller:
enabled: true
ingressGateways:
enabled: true
gateways:
- name: ingress-gateway
service:
type: LoadBalancer
meshGateway:
enabled: true

Testing and Validation

1. Integration Tests
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class ConsulConnectIntegrationTest {
@Container
static final ConsulContainer consulContainer = new ConsulContainer("consul:1.15")
.withCommand("agent", "-dev", "-client", "0.0.0.0");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.cloud.consul.host", consulContainer::getHost);
registry.add("spring.cloud.consul.port", consulContainer::getFirstMappedPort);
}
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private ConsulClient consulClient;
@Test
void testServiceRegistration() {
// Verify service is registered with Consul
Response<Map<String, Service>> services = consulClient.getAgentServices();
assertThat(services.getValue()).containsKey("user-service");
}
@Test
void testServiceDiscovery() {
// Test service discovery
Response<List<ServiceHealth>> healthyServices = consulClient.getHealthServices(
"user-service", true, QueryParams.DEFAULT);
assertThat(healthyServices.getValue()).isNotEmpty();
}
}
2. Health Check Verification
@Component
public class ConsulHealthIndicator implements HealthIndicator {
private final ConsulClient consulClient;
public ConsulHealthIndicator(ConsulClient consulClient) {
this.consulClient = consulClient;
}
@Override
public Health health() {
try {
// Check Consul connectivity
consulClient.getStatusLeader();
return Health.up()
.withDetail("consul", "connected")
.withDetail("timestamp", Instant.now())
.build();
} catch (Exception e) {
return Health.down()
.withDetail("consul", "disconnected")
.withDetail("error", e.getMessage())
.build();
}
}
}

Best Practices

  1. Use Sidecar Injection: Leverage automatic sidecar injection in Kubernetes
  2. Secure Intentions: Default to deny and explicitly allow necessary communication
  3. Health Checks: Implement comprehensive health checks for services
  4. Circuit Breakers: Use resilience patterns for service-to-service calls
  5. Monitoring: Monitor Connect metrics and service mesh performance
  6. Certificate Rotation: Rely on Connect's automatic certificate management
// Example of secure service communication
@Service
public class SecureServiceClient {
private final RestTemplate restTemplate;
private final ConnectAwareServiceDiscovery serviceDiscovery;
public ResponseEntity<String> callSecureService(String serviceName, String path) {
ServiceInstance instance = serviceDiscovery.getServiceInstance(serviceName);
// Verify service is Connect-enabled
if (!instance.isSecure()) {
throw new SecurityException("Service communication not secured: " + serviceName);
}
String url = String.format("https://%s:%d%s", 
instance.getHost(), instance.getPort(), path);
return restTemplate.getForEntity(url, String.class);
}
}

Conclusion

Consul Connect provides powerful service mesh capabilities for Java applications:

  • Automatic mTLS for secure service communication
  • Service discovery with health checking
  • Intentions-based authorization policies
  • Zero-trust networking with identity-based security

By integrating Consul Connect with Spring Boot applications, you can build secure, resilient microservices architectures that leverage the full power of service mesh technology without significant code changes. The sidecar proxy model ensures that security and networking concerns are abstracted away from business logic, while still providing robust security guarantees.

Pyroscope Profiling in Java
Explains how to use Pyroscope for continuous profiling in Java applications, helping developers analyze CPU and memory usage patterns to improve performance and identify bottlenecks.
https://macronepal.com/blog/pyroscope-profiling-in-java/

OpenTelemetry Metrics in Java: Comprehensive Guide
Provides a complete guide to collecting and exporting metrics in Java using OpenTelemetry, including counters, histograms, gauges, and integration with monitoring tools. (MACRO NEPAL)
https://macronepal.com/blog/opentelemetry-metrics-in-java-comprehensive-guide/

OTLP Exporter in Java: Complete Guide for OpenTelemetry
Explains how to configure OTLP exporters in Java to send telemetry data such as traces, metrics, and logs to monitoring systems using HTTP or gRPC protocols. (MACRO NEPAL)
https://macronepal.com/blog/otlp-exporter-in-java-complete-guide-for-opentelemetry/

Thanos Integration in Java: Global View of Metrics
Explains how to integrate Thanos with Java monitoring systems to create a scalable global metrics view across multiple Prometheus instances.

https://macronepal.com/blog/thanos-integration-in-java-global-view-of-metrics

Time Series with InfluxDB in Java: Complete Guide (Version 2)
Explains how to manage time-series data using InfluxDB in Java applications, including storing, querying, and analyzing metrics data.

https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide-2

Time Series with InfluxDB in Java: Complete Guide
Provides an overview of integrating InfluxDB with Java for time-series data handling, including monitoring applications and managing performance metrics.

https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide

Implementing Prometheus Remote Write in Java (Version 2)
Explains how to configure Java applications to send metrics data to Prometheus-compatible systems using the remote write feature for scalable monitoring.

https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide-2

Implementing Prometheus Remote Write in Java: Complete Guide
Provides instructions for sending metrics from Java services to Prometheus servers, enabling centralized monitoring and real-time analytics.

https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide

Building a TileServer GL in Java: Vector and Raster Tile Server
Explains how to build a TileServer GL in Java for serving vector and raster map tiles, useful for geographic visualization and mapping applications.

https://macronepal.com/blog/building-a-tileserver-gl-in-java-vector-and-raster-tile-server

Indoor Mapping in Java
Explains how to create indoor mapping systems in Java, including navigation inside buildings, spatial data handling, and visualization techniques.

Leave a Reply

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


Macro Nepal Helper