Skip to content

Spring Boot Template

The spring-boot template generates a production-ready Kotlin + Spring Boot 3 backend. It is the default backend when you run blissful-infra start without specifying --backend.

ComponentVersionPurpose
Kotlin1.9+Application language
Spring Boot3.xWeb framework, dependency injection
Spring KafkaKafka producer and consumer
Spring Data JPADatabase access via Hibernate ORM
Spring WebSocketReal-time bidirectional communication
Spring Cache@Cacheable Redis integration
FlywayDatabase schema migrations
Gradle8.xBuild tool with Kotlin DSL
OpenTelemetry Java agent2.xAuto-instrumentation for traces
backend/
├── src/main/kotlin/com/blissful/{{project_name}}/
│ ├── Application.kt # Spring Boot entry point
│ ├── config/
│ │ ├── KafkaConfig.kt # Topic creation + serializers
│ │ └── WebSocketConfig.kt # STOMP endpoint configuration
│ ├── controller/
│ │ └── MessageController.kt # REST + WebSocket message handlers
│ ├── service/
│ │ └── MessageService.kt # Business logic, Kafka publish
│ ├── consumer/
│ │ └── MessageConsumer.kt # Kafka listener → WebSocket broadcast
│ ├── model/
│ │ └── Message.kt # JPA entity (with @IF_POSTGRES block)
│ └── repository/
│ └── MessageRepository.kt # Spring Data JPA repository
├── src/main/resources/
│ ├── application.yml # App config (Kafka, DB, Redis, OTel)
│ └── db/migration/
│ └── V1__create_messages.sql # Flyway initial schema
├── build.gradle.kts # Gradle build with all dependencies
├── Dockerfile # Multi-stage build, includes OTel agent
└── Jenkinsfile # CI/CD pipeline definition

The generated app implements a simple real-time chat to demonstrate the full stack working together. The flow:

  1. Browser sends a message via WebSocket (/ws/send)
  2. Spring controller receives it and publishes to the messages Kafka topic
  3. A @KafkaListener in MessageConsumer.kt consumes from the topic
  4. The consumer broadcasts the message to all connected WebSocket clients via SimpMessagingTemplate
  5. Browser receives the broadcast on subscription /topic/messages
  6. Message is persisted to Postgres via MessageRepository.save() (if database includes postgres)
  7. Recent messages are cached in Redis (if using postgres-redis)
MethodPathDescription
GET/api/healthSimple health check
GET/actuator/healthSpring Boot health endpoint (used by Docker healthcheck)
GET/actuator/prometheusPrometheus metrics scrape endpoint
POST/api/messagesSend a message via REST
GET/api/messagesGet recent messages
WS/wsSTOMP WebSocket endpoint

The template creates two topics at startup:

TopicPartitionsPurpose
messages3Chat messages flowing backend → consumer → clients
events3General event bus (used by AI pipeline plugin when enabled)

Kafka is configured in application.yml using the KAFKA_BOOTSTRAP_SERVERS environment variable (set to kafka:9094 inside Docker, localhost:9092 locally).

  • MessageRepository extends JpaRepository<Message, Long>
  • Flyway migration at db/migration/V1__create_messages.sql creates the messages table
  • DATABASE_URL, DB_USERNAME, and DB_PASSWORD environment variables are read from the Docker Compose environment
  • Connection pool configured for local development (max 5 connections)

All of the above, plus:

  • MessageService is annotated with @Cacheable(value = "messages") on reads
  • @CacheEvict(value = "messages", allEntries = true) on writes
  • Redis cache is configured via REDIS_URL environment variable
  • Cache hits/misses are visible in the Grafana JVM dashboard under custom metrics

The JPA, Flyway, and repository layers are omitted entirely. The app runs without any database dependency.

The Dockerfile copies the OpenTelemetry Java agent JAR into the image and adds -javaagent:/otel-agent.jar via JAVA_TOOL_OPTIONS. This instruments all HTTP requests, Kafka produces/consumes, and JDBC queries automatically. Traces are exported to Jaeger at http://jaeger:4318 (OTLP/HTTP).

Open Jaeger at http://localhost:16686 and search for service <project-name>-backend to see traces.

Spring Actuator exposes Prometheus-format metrics at /actuator/prometheus. Prometheus scrapes this endpoint every 15 seconds. Pre-built Grafana dashboards display:

  • JVM heap usage, GC pause times, thread count
  • HTTP request rate, latency percentiles (p50/p95/p99), error rate
  • Kafka consumer lag
  • Cache hit/miss ratio (with Redis)

All container logs are collected by Promtail and shipped to Loki. Logs are queryable in Grafana using LogQL or through the Dashboard’s Logs tab with full-text search.

The generated backend/Jenkinsfile defines a declarative pipeline with these stages:

pipeline {
stages {
stage('Checkout') { ... } // git checkout from local path
stage('Build') { ... } // ./gradlew build
stage('Test') { ... } // ./gradlew test (JUnit results published)
stage('Docker') { ... } // docker build -t localhost:5050/<name>:<build>
stage('Push') { ... } // docker push localhost:5050/<name>:<build>
}
}

Trigger a build from the CLI:

Terminal window
blissful-infra jenkins build my-app

For the fastest inner loop when editing backend code, use Spring Boot DevTools mode:

Terminal window
cd my-app
blissful-infra dev
# Starts Spring Boot DevTools — saves trigger JVM restart in ~2-3 seconds

Or for template development (editing the template source itself):

Terminal window
# Terminal 1
blissful-infra dev --templates dev-app
# Terminal 2 — continuous Kotlin compilation
cd dev-app/backend && ./gradlew classes -t