- Published on
11 - Spring Native & Cloud-Native Development
- Authors

- Name
- Samreach YAN
Table of Contents
- Project Setup
- Spring Native Compilation
- Containerization with Docker
- Kubernetes Deployment
- Cloud Configuration
- Observability
- Complete Sample Application
Prerequisites
- JDK 17 or later
- Maven 3.8+
- Basic knowledge of Spring Boot and reactive programming
Project Setup
Folder Structure
spring-native-cloud/
├── src/
│ ├── main/
│ │ ├── java/com/example/demo/
│ │ │ ├── DemoApplication.java
│ │ │ ├── controller/
│ │ │ │ └── GreetingController.java
│ │ │ ├── config/
│ │ │ │ └── CloudConfig.java
│ │ │ └── service/
│ │ │ └── GreetingService.java
│ │ ├── resources/
│ │ │ ├── application.properties
│ │ │ ├── bootstrap.properties
│ │ │ └── k8s/
│ │ │ ├── deployment.yaml
│ │ │ └── service.yaml
│ │ └── docker/
│ │ └── Dockerfile
├── .dockerignore
├── pom.xml
└── README.md
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Spring Native Cloud Demo</description>
<properties>
<java.version>17</java.version>
<spring-native.version>0.12.1</spring-native.version>
</properties>
<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- Observability -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
<!-- Spring Native -->
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>${spring-native.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.9.28</version>
<executions>
<execution>
<id>build-native</id>
<phase>package</phase>
<goals>
<goal>compile-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Spring Native Compilation
DemoApplication.java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.nativex.hint.TypeHint;
@TypeHint(
types = {
com.example.demo.controller.GreetingController.class,
com.example.demo.service.GreetingService.class
},
typeNames = {
"com.example.demo.config.CloudConfig"
}
)
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Build Native Image
mvn -Pnative spring-boot:build-image
Or for direct native compilation:
mvn -Pnative clean package
Containerization with Docker
Dockerfile
# Stage 1: Build the application
FROM eclipse-temurin:17-jdk-jammy as builder
WORKDIR /workspace/app
COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
COPY src src
RUN ./mvnw install -DskipTests
# Stage 2: Create the production image
FROM eclipse-temurin:17-jre-jammy
WORKDIR /app
COPY /workspace/app/target/*.jar app.jar
# For native image
# COPY --from=builder /workspace/app/target/demo app
EXPOSE 8080
# For JVM
ENTRYPOINT ["java", "-jar", "app.jar"]
# For native image
# ENTRYPOINT ["./demo"]
.dockerignore
.git
.gitignore
target
.mvn
mvnw
mvnw.cmd
*.iml
.idea
*.log
*.jar
Dockerfile
Build and Run Docker Image
docker build -t spring-native-cloud-demo .
docker run -p 8080:8080 spring-native-cloud-demo
Kubernetes Deployment
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-native-cloud-demo
labels:
app: spring-native-cloud-demo
spec:
replicas: 3
selector:
matchLabels:
app: spring-native-cloud-demo
template:
metadata:
labels:
app: spring-native-cloud-demo
annotations:
prometheus.io/scrape: 'true'
prometheus.io/port: '8080'
prometheus.io/path: '/actuator/prometheus'
spec:
containers:
- name: spring-native-cloud-demo
image: spring-native-cloud-demo
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 15
periodSeconds: 5
resources:
requests:
cpu: '500m'
memory: '512Mi'
limits:
cpu: '1000m'
memory: '1024Mi'
env:
- name: SPRING_PROFILES_ACTIVE
value: 'kubernetes'
service.yaml
apiVersion: v1
kind: Service
metadata:
name: spring-native-cloud-demo-service
spec:
selector:
app: spring-native-cloud-demo
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
Deploy to Kubernetes
kubectl apply -f src/main/resources/k8s/deployment.yaml
kubectl apply -f src/main/resources/k8s/service.yaml
Cloud Configuration
bootstrap.properties
spring.application.name=spring-native-cloud-demo
spring.cloud.config.uri=http://localhost:8888
spring.cloud.config.fail-fast=true
CloudConfig.java
package com.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.cloud.config.server.EnableConfigServer;
@Configuration
@EnableConfigServer
public class CloudConfig {
}
application.properties
# Server
server.port=8080
# Actuator
management.endpoints.web.exposure.include=health,info,prometheus,metrics,loggers
management.endpoint.health.probes.enabled=true
management.endpoint.health.show-details=always
management.health.livenessState.enabled=true
management.health.readinessState.enabled=true
# Tracing
management.tracing.sampling.probability=1.0
spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.probability=1.0
# Native hints
spring.aop.proxy-target-class=false
Observability
GreetingService.java
package com.example.demo.service;
import io.micrometer.observation.annotation.Observed;
import org.springframework.stereotype.Service;
@Service
public class GreetingService {
@Observed(
name = "greeting.service",
contextualName = "greeting-service",
lowCardinalityKeyValues = {"serviceType", "greeting"}
)
public String greet(String name) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("Name cannot be empty");
}
return "Hello, " + name + "!";
}
}
GreetingController.java
package com.example.demo.controller;
import com.example.demo.service.GreetingService;
import io.micrometer.core.annotation.Timed;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Timed(value = "greeting.controller", description = "Time taken to process greeting requests")
public class GreetingController {
private final GreetingService greetingService;
public GreetingController(GreetingService greetingService) {
this.greetingService = greetingService;
}
@GetMapping("/greet")
public String greet(@RequestParam(value = "name", defaultValue = "World") String name) {
return greetingService.greet(name);
}
@GetMapping("/health")
public String health() {
return "OK";
}
}
Complete Sample Application
Running the Application
- Build Native Image:
mvn -Pnative spring-boot:build-image
- Run with Docker:
docker run -p 8080:8080 spring-native-cloud-demo
- Access Endpoints:
- Application:
http://localhost:8080/greet?name=Spring - Actuator:
http://localhost:8080/actuator - Prometheus Metrics:
http://localhost:8080/actuator/prometheus - Health:
http://localhost:8080/actuator/health
Kubernetes Deployment
- Build and push Docker image:
docker build -t your-repo/spring-native-cloud-demo:1.0.0 .
docker push your-repo/spring-native-cloud-demo:1.0.0
- Update deployment.yaml with your image:
image: your-repo/spring-native-cloud-demo:1.0.0
- Deploy to Kubernetes:
kubectl apply -f src/main/resources/k8s/deployment.yaml
kubectl apply -f src/main/resources/k8s/service.yaml
This complete tutorial provides everything you need to build, containerize, and deploy a Spring Native application with cloud-native features including observability and Kubernetes support.