Cloud-Native Microservices: Mastering Micronaut with Kubernetes

Article

Micronaut is a modern JVM-based framework designed for building modular, easily testable microservices and serverless applications. When combined with Kubernetes, it provides a powerful platform for building scalable, resilient cloud-native applications with minimal resource consumption and fast startup times.


What is Micronaut Kubernetes Integration?

Micronaut provides first-class support for Kubernetes through dedicated modules that enable service discovery, configuration management, client-side load balancing, and health checks specifically designed for Kubernetes environments.

Key Benefits for Java Developers:

  • Fast Startup Time: Compile-time dependency injection reduces startup time
  • Low Memory Footprint: Minimal runtime reflection usage
  • Native Kubernetes Support: Built-in service discovery and configuration
  • Reactive Programming: Built-in support for reactive streams
  • GraalVM Native Image: Support for native compilation
  • Minimal Boilerplate: Reduced configuration through annotations

Setup and Dependencies

1. Maven Configuration

<!-- pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>micronaut-kubernetes-app</artifactId>
<version>1.0.0</version>
<properties>
<micronaut.version>4.2.0</micronaut.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<exec.mainClass>com.example.Application</exec.mainClass>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-bom</artifactId>
<version>${micronaut.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Micronaut Core -->
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-server-netty</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-client</artifactId>
<scope>compile</scope>
</dependency>
<!-- Kubernetes Integration -->
<dependency>
<groupId>io.micronaut.kubernetes</groupId>
<artifactId>micronaut-kubernetes-discovery-client</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut.kubernetes</groupId>
<artifactId>micronaut-kubernetes-client</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut.kubernetes</groupId>
<artifactId>micronaut-kubernetes-informer</artifactId>
<scope>compile</scope>
</dependency>
<!-- Health Checks -->
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-management</artifactId>
<scope>compile</scope>
</dependency>
<!-- Configuration -->
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-runtime</artifactId>
<scope>compile</scope>
</dependency>
<!-- Metrics -->
<dependency>
<groupId>io.micronaut.micrometer</groupId>
<artifactId>micronaut-micrometer-core</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut.micrometer</groupId>
<artifactId>micronaut-micrometer-registry-prometheus</artifactId>
<scope>compile</scope>
</dependency>
<!-- Validation -->
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-validation</artifactId>
<scope>compile</scope>
</dependency>
<!-- Testing -->
<dependency>
<groupId>io.micronaut.test</groupId>
<artifactId>micronaut-test-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.micronaut.maven</groupId>
<artifactId>micronaut-maven-plugin</artifactId>
<version>${micronaut.version}</version>
</plugin>
</plugins>
</build>
</project>

2. Gradle Configuration

// build.gradle
plugins {
id("java")
id("io.micronaut.application") version "4.2.0"
}
version = "1.0.0"
group = "com.example"
repositories {
mavenCentral()
}
micronaut {
runtime("netty")
testRuntime("junit5")
processing {
incremental(true)
annotations("com.example.*")
}
}
dependencies {
annotationProcessor("io.micronaut:micronaut-http-validation")
annotationProcessor("io.micronaut:micronaut-validation")
implementation("io.micronaut:micronaut-http-client")
implementation("io.micronaut:micronaut-http-server-netty")
implementation("io.micronaut:micronaut-runtime")
implementation("io.micronaut:micronaut-validation")
implementation("io.micronaut:micronaut-management")
// Kubernetes
implementation("io.micronaut.kubernetes:micronaut-kubernetes-discovery-client")
implementation("io.micronaut.kubernetes:micronaut-kubernetes-client")
implementation("io.micronaut.kubernetes:micronaut-kubernetes-informer")
// Metrics
implementation("io.micronaut.micrometer:micronaut-micrometer-core")
implementation("io.micronaut.micrometer:micronaut-micrometer-registry-prometheus")
// Logging
runtimeOnly("ch.qos.logback:logback-classic")
// Testing
testImplementation("io.micronaut.test:micronaut-test-junit5")
}
application {
mainClass.set("com.example.Application")
}
java {
sourceCompatibility = JavaVersion.toVersion("17")
targetCompatibility = JavaVersion.toVersion("17")
}

Basic Application Setup

1. Application Configuration

# src/main/resources/application.yml
micronaut:
application:
name: order-service
version: 1.0.0
server:
port: 8080
cors:
enabled: true
metrics:
enabled: true
export:
prometheus:
enabled: true
step: PT1M
descriptions: true
management:
endpoints:
enabled: true
health:
enabled: true
sensitive: false
details-visible: ANONYMOUS
info:
enabled: true
metrics:
enabled: true
loggers:
enabled: true
# Kubernetes Configuration
kubernetes:
client:
namespace: default
config:
from-api: true
discovery:
enabled: true
mode: endpoint
service-name: order-service
health-check:
enabled: true
port: 8080
path: /health
# Logging
logger:
levels:
io.micronaut.kubernetes: INFO
com.example: DEBUG
# Endpoints
endpoints:
health:
enabled: true
sensitive: false
path: /health
info:
enabled: true
sensitive: false
path: /info
metrics:
enabled: true
sensitive: false
path: /metrics
loggers:
enabled: true
write-sensitive: false
path: /loggers

2. Main Application Class

package com.example;
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String[] args) {
Micronaut.run(Application.class, args);
}
}

3. Basic REST Controller

package com.example.controller;
import io.micronaut.http.annotation.*;
import io.micronaut.http.HttpStatus;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;
import jakarta.validation.Valid;
import java.util.List;
@Controller("/api/orders")
@ExecuteOn(TaskExecutors.IO)
public class OrderController {
@Get
public List<Order> getOrders() {
return List.of(
new Order("1", "Pending", 99.99),
new Order("2", "Completed", 149.99)
);
}
@Get("/{id}")
public Order getOrder(String id) {
return new Order(id, "Processing", 199.99);
}
@Post
@Status(HttpStatus.CREATED)
public Order createOrder(@Body @Valid Order order) {
return order;
}
@Put("/{id}")
public Order updateOrder(String id, @Body @Valid Order order) {
return new Order(id, order.status(), order.amount());
}
@Delete("/{id}")
@Status(HttpStatus.NO_CONTENT)
public void deleteOrder(String id) {
// Delete logic
}
public record Order(String id, String status, double amount) {}
}

Kubernetes Service Discovery

1. Service Discovery Configuration

# src/main/resources/application.yml
kubernetes:
discovery:
enabled: true
mode: endpoint
includes:
- user-service
- inventory-service
- payment-service
health-check:
enabled: true
timeout: 5s
interval: 30s
port: 8080
path: /health
micronaut:
http:
services:
user-service:
urls:
- http://user-service:8080
inventory-service:
urls:
- http://inventory-service:8080
payment-service:
urls:
- http://payment-service:8080

2. Declarative HTTP Client

package com.example.client;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Header;
import io.micronaut.http.client.annotation.Client;
import io.reactivex.rxjava3.core.Single;
import jakarta.validation.Valid;
import java.util.List;
@Client(id = "user-service")  // Uses Kubernetes service discovery
public interface UserServiceClient {
@Get("/api/users/{userId}")
Single<User> getUser(String userId);
@Post("/api/users")
Single<User> createUser(@Body @Valid User user);
@Get("/api/users")
Single<List<User>> getUsers();
record User(String id, String name, String email, String status) {}
}
@Client(id = "inventory-service")
public interface InventoryServiceClient {
@Get("/api/inventory/{productId}")
Single<Inventory> getInventory(String productId);
@Post("/api/inventory/{productId}/reserve")
Single<Inventory> reserveInventory(String productId, int quantity);
record Inventory(String productId, int available, int reserved) {}
}

3. Using Service Discovery in Controller

package com.example.controller;
import com.example.client.UserServiceClient;
import com.example.client.InventoryServiceClient;
import io.micronaut.http.annotation.*;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;
import jakarta.inject.Inject;
import io.reactivex.rxjava3.core.Single;
@Controller("/api/orders")
@ExecuteOn(TaskExecutors.IO)
public class OrderProcessingController {
@Inject
private UserServiceClient userServiceClient;
@Inject
private InventoryServiceClient inventoryServiceClient;
@Post("/process")
public Single<OrderResponse> processOrder(@Body OrderRequest request) {
return userServiceClient.getUser(request.userId())
.flatMap(user -> {
if (!"ACTIVE".equals(user.status())) {
return Single.error(new RuntimeException("User is not active"));
}
return inventoryServiceClient.reserveInventory(request.productId(), request.quantity());
})
.map(inventory -> new OrderResponse(
"ORDER_CREATED", 
"Order processed successfully", 
inventory.available()
));
}
public record OrderRequest(String userId, String productId, int quantity) {}
public record OrderResponse(String status, String message, int availableQuantity) {}
}

Health Checks and Readiness

1. Custom Health Indicators

package com.example.health;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.health.HealthStatus;
import io.micronaut.management.health.indicator.HealthIndicator;
import io.micronaut.management.health.indicator.HealthResult;
import jakarta.inject.Singleton;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
@Singleton
public class DatabaseHealthIndicator implements HealthIndicator {
private final DatabaseService databaseService;
public DatabaseHealthIndicator(DatabaseService databaseService) {
this.databaseService = databaseService;
}
@Override
@NonNull
public Publisher<HealthResult> getResult() {
return Mono.fromCallable(() -> {
boolean isHealthy = databaseService.isConnected();
Map<String, Object> details = new HashMap<>();
details.put("database", "postgresql");
details.put("connection", isHealthy ? "established" : "failed");
HealthStatus status = isHealthy ? HealthStatus.UP : HealthStatus.DOWN;
return HealthResult.builder("database")
.status(status)
.details(details)
.build();
});
}
}
@Singleton
class ExternalServiceHealthIndicator implements HealthIndicator {
@Override
@NonNull
public Publisher<HealthResult> getResult() {
return Mono.fromCallable(() -> {
// Check external service connectivity
boolean isHealthy = checkExternalService();
Map<String, Object> details = new HashMap<>();
details.put("service", "payment-gateway");
details.put("responseTime", "45ms");
return HealthResult.builder("payment-service")
.status(isHealthy ? HealthStatus.UP : HealthStatus.DOWN)
.details(details)
.build();
});
}
private boolean checkExternalService() {
// Implementation to check external service
return true;
}
}

2. Kubernetes Liveness and Readiness

# src/main/resources/application.yml
endpoints:
health:
enabled: true
sensitive: false
details-visible: ANONYMOUS
liveness:
enabled: true
path: /health/liveness
readiness:
enabled: true
path: /health/readiness
info:
enabled: true
sensitive: false
management:
health:
defaults:
enabled: true
disk-space:
enabled: true
threshold: 100MB
cpu:
enabled: true
load-average: 4.0

3. Custom Readiness Check

package com.example.health;
import io.micronaut.context.annotation.Requires;
import io.micronaut.health.HealthStatus;
import io.micronaut.management.health.indicator.HealthIndicator;
import io.micronaut.management.health.indicator.HealthResult;
import jakarta.inject.Singleton;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
@Singleton
@Requires(bean = ApplicationStartupTracker.class)
public class ApplicationReadinessHealthIndicator implements HealthIndicator {
private final ApplicationStartupTracker startupTracker;
public ApplicationReadinessHealthIndicator(ApplicationStartupTracker startupTracker) {
this.startupTracker = startupTracker;
}
@Override
public Publisher<HealthResult> getResult() {
return Mono.fromCallable(() -> {
boolean isReady = startupTracker.isApplicationReady();
return HealthResult.builder("application")
.status(isReady ? HealthStatus.UP : HealthStatus.DOWN)
.detail("startup-complete", isReady)
.detail("services-initialized", startupTracker.getInitializedServices())
.build();
});
}
}
@Singleton
class ApplicationStartupTracker {
private volatile boolean applicationReady = false;
private volatile int initializedServices = 0;
public void markServiceInitialized() {
initializedServices++;
if (initializedServices >= 3) { // All required services initialized
applicationReady = true;
}
}
public boolean isApplicationReady() {
return applicationReady;
}
public int getInitializedServices() {
return initializedServices;
}
}

Kubernetes Configuration Management

1. ConfigMap and Secret Integration

# src/main/resources/application.yml
micronaut:
config-client:
enabled: true
kubernetes:
client:
config-maps:
enabled: true
names:
- app-config
- feature-flags
secrets:
enabled: true
names:
- database-secrets
- api-keys
# External configuration
kubernetes:
config:
enabled: true
sources:
- name: app-config
namespace: default
- name: feature-flags  
namespace: default
secrets:
- name: database-secrets
namespace: default

2. Configuration Beans

package com.example.config;
import io.micronaut.context.annotation.ConfigurationProperties;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
@ConfigurationProperties("app")
public class ApplicationConfig {
@NotBlank
private String name;
@NotBlank
private String version;
private DatabaseConfig database;
private SecurityConfig security;
private FeatureFlags features;
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public DatabaseConfig getDatabase() { return database; }
public void setDatabase(DatabaseConfig database) { this.database = database; }
public SecurityConfig getSecurity() { return security; }
public void setSecurity(SecurityConfig security) { this.security = security; }
public FeatureFlags getFeatures() { return features; }
public void setFeatures(FeatureFlags features) { this.features = features; }
@ConfigurationProperties("database")
public static class DatabaseConfig {
@NotBlank
private String url;
@NotBlank
private String username;
private int poolSize = 10;
private long connectionTimeout = 30000;
// Getters and Setters
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public int getPoolSize() { return poolSize; }
public void setPoolSize(int poolSize) { this.poolSize = poolSize; }
public long getConnectionTimeout() { return connectionTimeout; }
public void setConnectionTimeout(long connectionTimeout) { 
this.connectionTimeout = connectionTimeout; 
}
}
@ConfigurationProperties("security")
public static class SecurityConfig {
private boolean enabled = true;
private String jwtSecret;
private long tokenExpiration = 3600000; // 1 hour
// Getters and Setters
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public String getJwtSecret() { return jwtSecret; }
public void setJwtSecret(String jwtSecret) { this.jwtSecret = jwtSecret; }
public long getTokenExpiration() { return tokenExpiration; }
public void setTokenExpiration(long tokenExpiration) { 
this.tokenExpiration = tokenExpiration; 
}
}
@ConfigurationProperties("features")
public static class FeatureFlags {
private boolean newSearchEnabled = false;
private boolean premiumFeatures = false;
private boolean experimentalApi = false;
// Getters and Setters
public boolean isNewSearchEnabled() { return newSearchEnabled; }
public void setNewSearchEnabled(boolean newSearchEnabled) { 
this.newSearchEnabled = newSearchEnabled; 
}
public boolean isPremiumFeatures() { return premiumFeatures; }
public void setPremiumFeatures(boolean premiumFeatures) { 
this.premiumFeatures = premiumFeatures; 
}
public boolean isExperimentalApi() { return experimentalApi; }
public void setExperimentalApi(boolean experimentalApi) { 
this.experimentalApi = experimentalApi; 
}
}
}

3. Using Configuration in Services

package com.example.service;
import com.example.config.ApplicationConfig;
import jakarta.inject.Singleton;
import jakarta.inject.Inject;
@Singleton
public class OrderService {
private final ApplicationConfig config;
private final DatabaseService databaseService;
@Inject
public OrderService(ApplicationConfig config, DatabaseService databaseService) {
this.config = config;
this.databaseService = databaseService;
}
public void processOrder(Order order) {
// Use feature flags
if (config.getFeatures().isNewSearchEnabled()) {
// Use new search algorithm
processWithNewAlgorithm(order);
} else {
// Use legacy algorithm
processWithLegacyAlgorithm(order);
}
// Use database configuration
databaseService.configure(
config.getDatabase().getUrl(),
config.getDatabase().getUsername(),
config.getDatabase().getPoolSize()
);
}
private void processWithNewAlgorithm(Order order) {
// New processing logic
}
private void processWithLegacyAlgorithm(Order order) {
// Legacy processing logic
}
}

Kubernetes Deployment Manifests

1. Deployment Manifest

# k8s/deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
namespace: default
labels:
app: order-service
version: "1.0.0"
team: backend
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
version: "1.0.0"
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"
spec:
serviceAccountName: order-service-account
containers:
- name: order-service
image: my-registry/order-service:1.0.0
ports:
- containerPort: 8080
name: http
- containerPort: 9090
name: management
env:
- name: JAVA_OPTS
value: "-Xmx512m -Xms256m -XX:+UseG1GC"
- name: MICRONAUT_ENVIRONMENTS
value: "kubernetes"
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
livenessProbe:
httpGet:
path: /health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/readiness
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
startupProbe:
httpGet:
path: /health/readiness
port: 8080
initialDelaySeconds: 0
periodSeconds: 5
failureThreshold: 30
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
---
apiVersion: v1
kind: Service
metadata:
name: order-service
labels:
app: order-service
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
- port: 9090
targetPort: 9090
protocol: TCP
name: management
selector:
app: order-service
type: ClusterIP

2. ConfigMap and Secrets

# k8s/config.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
application.yml: |
app:
name: order-service
version: 1.0.0
database:
url: jdbc:postgresql://postgresql:5432/orders
poolSize: 15
connectionTimeout: 30000
features:
newSearchEnabled: true
premiumFeatures: false
experimentalApi: false
logging:
level:
com.example: INFO
---
apiVersion: v1
kind: Secret
metadata:
name: database-secrets
type: Opaque
data:
database-password: cG9zdGdyZXMxMjM=  # base64 encoded
api-key: bXktc2VjcmV0LWFwaS1rZXk=    # base64 encoded

3. Horizontal Pod Autoscaler

# k8s/hpa.yml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-service
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-service
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "100"

Advanced Features

1. Kubernetes Informers

package com.example.informer;
import io.kubernetes.client.informer.ResourceEventHandler;
import io.kubernetes.client.informer.SharedIndexInformer;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.micronaut.kubernetes.client.informer.Informer;
import jakarta.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class ConfigMapInformer {
private static final Logger LOG = LoggerFactory.getLogger(ConfigMapInformer.class);
@Informer(apiType = V1ConfigMap.class)
public SharedIndexInformer<V1ConfigMap> configMapInformer() {
return new SharedIndexInformer<V1ConfigMap>() {
@Override
public void addEventHandler(ResourceEventHandler<V1ConfigMap> handler) {
// Handle ConfigMap events
}
};
}
public void onConfigMapAdded(V1ConfigMap configMap) {
LOG.info("ConfigMap added: {}", configMap.getMetadata().getName());
// Update application configuration dynamically
}
public void onConfigMapUpdated(V1ConfigMap oldConfigMap, V1ConfigMap newConfigMap) {
LOG.info("ConfigMap updated: {}", newConfigMap.getMetadata().getName());
// Handle configuration updates
}
public void onConfigMapDeleted(V1ConfigMap configMap) {
LOG.info("ConfigMap deleted: {}", configMap.getMetadata().getName());
// Handle configuration removal
}
}

2. Distributed Tracing

package com.example.tracing;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.tracing.annotation.NewSpan;
import io.micronaut.tracing.annotation.SpanTag;
import jakarta.inject.Inject;
@Controller("/api/orders")
public class TracedOrderController {
@Inject
private OrderService orderService;
@Get("/{orderId}")
@NewSpan("get-order-details")
public Order getOrder(@SpanTag("order.id") String orderId) {
return orderService.findOrder(orderId);
}
@Get("/{orderId}/history")
@NewSpan("get-order-history")
public OrderHistory getOrderHistory(@SpanTag("order.id") String orderId) {
return orderService.getOrderHistory(orderId);
}
}

Build and Deployment

1. Dockerfile

FROM eclipse-temurin:17-jre as builder
WORKDIR /app
COPY target/micronaut-kubernetes-app-*.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract
FROM eclipse-temurin:17-jre
RUN addgroup -g 1000 -S micronaut && adduser -u 1000 -S micronaut -G micronaut
USER micronaut
WORKDIR /app
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
EXPOSE 8080 9090
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

2. Maven Build Script

#!/bin/bash
# build-and-deploy.sh
set -e
ENVIRONMENT=${1:-dev}
VERSION=${2:-1.0.0}
echo "Building Micronaut application for $ENVIRONMENT"
# Build application
mvn clean package -DskipTests
# Build Docker image
docker build -t my-registry/order-service:$VERSION .
if [ "$ENVIRONMENT" == "prod" ]; then
# Push to registry
docker push my-registry/order-service:$VERSION
# Deploy to Kubernetes
kubectl apply -f k8s/
kubectl rollout status deployment/order-service
fi
echo "Build and deployment completed successfully!"

Best Practices

1. Resource Optimization

  • Use compile-time dependency injection
  • Enable GraalVM native image for production
  • Configure proper resource limits
  • Use reactive programming for I/O operations

2. Monitoring and Observability

  • Enable Micrometer metrics
  • Configure distributed tracing
  • Use structured logging
  • Implement comprehensive health checks

3. Security

  • Use Kubernetes service accounts
  • Implement security contexts
  • Use secrets for sensitive data
  • Enable TLS for service communication

Conclusion

Micronaut with Kubernetes provides a powerful combination for building cloud-native Java applications:

  • Fast Startup: Compile-time DI enables rapid startup times
  • Low Memory: Minimal runtime reflection reduces memory footprint
  • Kubernetes Native: Built-in service discovery and configuration
  • Reactive Ready: Built-in support for reactive programming
  • Production Ready: Comprehensive health checks and metrics

By leveraging Micronaut's Kubernetes integration, Java teams can build highly scalable, resilient microservices that are optimized for cloud-native environments while maintaining developer productivity and application performance.

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