Reference service example

Spring Boot microservices example with Spring Middleware

This page shows a concrete reference setup based on a multi-module Spring Boot project. It includes a root aggregator, a minimal catalog-service, realistic application.yml configuration, and an example of a declarative client calling another service through Spring Middleware.

Multi-module Maven setup Catalog service example Declarative clients Kafka and GraphQL config Security and logging Redis and registry integration

What this example shows

This is not a toy snippet. It shows how a real Spring Middleware service can be structured and configured inside a multi-module project.

  • Root Maven aggregator
  • Minimal bootable service module
  • Concrete runtime configuration
  • Generated declarative client usage
  • Infrastructure integration points

Why this page exists

The getting started guide explains the platform model. This page shows what a concrete service looks like when those pieces are put together in a real project.

  • Use it as a starting point for new services.
  • Adapt the modules to your actual runtime needs.
  • Keep the structure explicit rather than hiding configuration.

Root aggregator pom.xml

The reference project uses a root aggregator that defines versions and includes the service modules. This is the entry point for building the whole multi-module setup.

reference-service / 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>

    <groupId>io.github.spring-middleware</groupId>
    <artifactId>reference-service</artifactId>
    <version>1.2.0</version>
    <packaging>pom</packaging>

    <name>spring-middleware-reference-service</name>
    <description>Reference microservices built on Spring Middleware</description>
    <url>https://github.com/Spring-Middleware/reference-service</url>

    <properties>

        <catalog.version>1.2.0</catalog.version>
        <product.version>1.2.0</product.version>

        <spring.boot.version>3.4.2</spring.boot.version>
        <spring-middleware.api.version>1.3.0</spring-middleware.api.version>
        <spring-middleware.app.version>1.6.0</spring-middleware.app.version>
        <spring-middleware.mongo-core.version>1.4.0</spring-middleware.mongo-core.version>
        <spring-middleware.kafka-core.version>1.3.0</spring-middleware.kafka-core.version>
        <spring-middleware.graphql.version>1.3.0</spring-middleware.graphql.version>
        <springdoc.openapi.version>2.8.4</springdoc.openapi.version>

        <java.version>21</java.version>
        <maven.compiler.release>${java.version}</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    </properties>

    <modules>
        <module>catalog-service</module>
        <module>product-service</module>
    </modules>

    <dependencyManagement>
        <dependencies>

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>io.github.spring-middleware</groupId>
                <artifactId>api</artifactId>
                <version>${spring-middleware.api.version}</version>
            </dependency>

            <dependency>
                <groupId>io.github.spring-middleware</groupId>
                <artifactId>app</artifactId>
                <version>${spring-middleware.app.version}</version>
            </dependency>

            <dependency>
                <groupId>io.github.spring-middleware</groupId>
                <artifactId>mongo-core</artifactId>
                <version>${spring-middleware.mongo-core.version}</version>
            </dependency>

            <dependency>
                <groupId>io.github.spring-middleware</groupId>
                <artifactId>kafka-core</artifactId>
                <version>${spring-middleware.kafka-core.version}</version>
            </dependency>

            <dependency>
                <groupId>io.github.spring-middleware</groupId>
                <artifactId>graphql</artifactId>
                <version>${spring-middleware.graphql.version}</version>
            </dependency>

            <dependency>
                <groupId>org.springdoc</groupId>
                <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
                <version>${springdoc.openapi.version}</version>
            </dependency>

        </dependencies>
    </dependencyManagement>

</project>

Minimal catalog-service module

Inside the aggregator, each service module keeps the parent reference and depends only on the Spring Middleware pieces it actually needs.

catalog-service / pom.xml

<parent>
  <groupId>io.github.spring-middleware</groupId>
  <artifactId>reference-service</artifactId>
  <version>1.2.0</version>
</parent>

<dependencies>
  <dependency>
    <groupId>io.github.spring-middleware</groupId>
    <artifactId>api</artifactId>
  </dependency>
  <dependency>
    <groupId>io.github.spring-middleware</groupId>
    <artifactId>app</artifactId>
  </dependency>
  <!-- Add other modules as needed (kafka-core, graphql, jpa, etc.) -->
</dependencies>

Spring Boot main class

package io.github.example.catalog;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CatalogApplication {
    static void main(String[] args) {
        SpringApplication.run(CatalogApplication.class, args);
    }
}

Example application.yml

This configuration shows how a real service can combine MongoDB, Kafka, GraphQL, client configuration, security, logging, registry registration, and Redis-backed runtime pieces.

catalog-service / application.yml

server:
  port: ${SERVER_PORT:8080}
  servlet:
    context-path: ${SERVER_CONTEXT_PATH:/catalog}

spring:
  data:
    mongodb:
      uri: ${MONGO_URI:mongodb://localhost:27017/catalog}
      uuid-representation: standard
  kafka:
    producer:
      properties:
        max.block.ms: 10000
        delivery.timeout.ms: 10000
        request.timeout.ms: 5000

springdoc:
  api-docs:
    path: /api-docs
  swagger-ui:
    path: /swagger-ui.html

logging:
  level:
    io.github.spring.middleware: ${LOG_LEVEL_PACKAGE:INFO}
    io.github.spring.middleware.scheduler: ERROR
    io.github.spring.middleware.jms: ERROR
    org.springframework.security: ERROR
    org.springframework.security.web: ERROR
    root: ${LOG_LEVEL_ROOT:ERROR}

middleware:
  log:
    apiKey: ${LOG_API_KEY}
    request:
      enabled: ${LOG_REQUEST_ENABLED:true}
    response:
      enabled: ${LOG_RESPONSE_ENABLED:true}
    responseTime:
      enabled: ${LOG_RESPONSE_TIME_ENABLED:false}
    exclude:
      urlPatterns:
        - /api-docs/**
        - /swagger-ui.html
        - /swagger-ui/**
        - /_alive
        - /graphql
        - /graphql/_alive
        - /graphql/schema-metadata

  jms:
    profile: ${JMS_PROFILE:LOCAL}
    host: ${JMS_HOST:amqp://rabbitmq:5672}
    user: ${JMS_USER:admin}
    password: ${JMS_PASSWORD:admin}
    max-pool-size: ${JMS_MAX_POOL_SIZE:10}
    min-idle: ${JMS_MIN_IDLE:5}
    max-idle: ${JMS_MAX_IDLE:10}
    rabbitmq:
      base-url: ${JMS_RABBITMQ_BASE_URL:http://rabbitmq:15672}
      registry:
        scanner:
          enabled: ${JMS_RABBITMQ_REGISTRY_SCANNER_ENABLED:true}
          check-interval: ${JMS_RABBITMQ_REGISTRY_SCANNER_CHECK_INTERVAL:10000}

  kafka:
    enabled: ${KAFKA_ENABLED:false}
    bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS}
    create-missing-topics: true
    topics:
      catalog-events:
        partitions: 5
        replication-factor: 3

    error-handling:
      enabled: ${KAFKA_ERROR_HANDLING_ENABLED:true}
      max-retries: ${KAFKA_ERROR_HANDLING_MAX_RETRIES:3}
      retry-backoff-ms: ${KAFKA_ERROR_HANDLING_RETRY_BACKOFF_MS:1000}
      dead-letter:
        enabled: ${KAFKA_ERROR_HANDLING_DEAD_LETTER_ENABLED:true}
        suffix: .DLT

    publishers:
      catalog:
        topic: ${KAFKA_TOPIC_CATALOG:catalog-events}

    subscribers:
      catalog:
        group-id: ${KAFKA_GROUP_ID_CATALOG:catalog-service-group}
        topic: ${KAFKA_TOPIC_CATALOG:catalog-events}
        concurrency: 3

  client:
    registry-endpoint: ${REGISTRY_ENDPOINT:http://localhost:8080/registry}
    product:
      security:
        type: OAUTH2_CLIENT_CREDENTIALS
        api-key: ${API_KEY_PRODUCT_SERVICE:default-product-api-key}
        listProducts:
          api-key: ${API_KEY_PRODUCT_SERVICE:default-product-list-api-key}
        oauth2:
          client-id: ${OAUTH2_CLIENT_ID_PRODUCT_SERVICE:product-service}
          client-secret: ${OAUTH2_CLIENT_SECRET_PRODUCT_SERVICE}
          token-uri: ${OAUTH2_TOKEN_URI_PRODUCT_SERVICE:http://keycloak:8080/realms/spring-middleware/protocol/openid-connect/token}

      connection:
        timeout: 500
        max-retries: 0

      circuit-breaker:
        wait-duration-in-open-state-ms: 10000

  resourceRegister:
    clusterName: ${RESOURCE_CLUSTER_NAME:catalog}

  public-server:
    host: ${PUBLIC_SERVER_HOST:localhost}
    port: ${PUBLIC_SERVER_PORT:8080}

  graphql:
    enabled: ${GRAPHQL_ENABLED:true}
    clusterName: ${GRAPHQL_CATALOG_CLUSTER_NAME:catalog}
    namespace: ${GRAPHQL_CATALOG_NAMESPACE:catalog}

  registry-consistency-scheduler:
    enabled: ${REGISTRY_CONSISTENCY_SCHEDULER_ENABLED:true}
    cron: ${REGISTRY_CONSISTENCY_SCHEDULER_CRON:*/10 * * * * *}

  security:
    type: ${SECURITY_TYPE:OAUTH2}
    public-paths:
      - /graphql
      - /resources/register
      - /api-docs/**
      - /swagger-ui.html
      - /swagger-ui/**
      - /start-publishing
      - /stop-publishing

    protected-paths:
      - type: ROLES
        path: /api/*/catalogs/*/products
        methods: [ POST ]
        allowed-roles: [ ADD_PRODUCTS_TO_CATALOG, ADMIN ]

      - type: ROLES
        path: /api/*/catalogs/*/products
        methods: [ GET ]
        allowed-roles: [ LIST_CATALOG_PRODUCTS, ADMIN ]

      - type: ROLES
        path: /api/*/catalogs/*/products
        methods: [ DELETE ]
        allowed-roles: [ REMOVE_PRODUCTS_FROM_CATALOG, ADMIN ]

      - type: ROLES
        path: /api/*/catalogs/*/products
        methods: [ PUT ]
        allowed-roles: [ REPLACE_PRODUCTS_FROM_CATALOG, ADMIN ]

      - type: ROLES
        path: /api/*/catalogs/*
        methods: [ GET ]
        query-params:
          - name: expand
            required: true
            values:
              - products
        allowed-roles: [ LIST_CATALOG_PRODUCTS, ADMIN ]

      - type: ROLES
        path: /api/*/catalogs/*
        methods: [ GET ]
        allowed-roles: [ GET_CATALOG, ADMIN ]

      - type: ROLES
        path: /api/*/catalogs/*
        methods: [ PATCH, PUT ]
        allowed-roles: [ UPDATE_CATALOG, ADMIN ]

      - type: ROLES
        path: /api/*/catalogs/*
        methods: [ DELETE ]
        allowed-roles: [ DELETE_CATALOG, ADMIN ]

      - type: ROLES
        path: /api/*/catalogs
        methods: [ POST ]
        allowed-roles: [ CREATE_CATALOG, ADMIN ]

      - type: ROLES
        path: /api/*/catalogs
        methods: [ GET ]
        allowed-roles: [ LIST_CATALOGS, ADMIN ]

    basic-auth:
      credentials:
        - username: ${BASIC_AUTH_CATALOG_ADMIN}
          password: ${BASIC_AUTH_CATALOG_ADMIN_PASSWORD}
          roles: [ ADMIN ]

    jwt:
      secret: ${JWT_SECRET:dfwzsdzwh823zebdwdz772632gdsbddfr4}

    oauth2:
      jwk-set-uri: ${OAUTH2_ISSUER_URI_JWK_SET_URI:http://keycloak:8080/realms/spring-middleware/protocol/openid-connect/certs}
      authorities-claim-path: ${OAUTH2_ISSUER_URI_AUTHORITIES_CLAIM_PATH:$.resource_access.catalog-service.roles[*]}

    api-key:
      header-name: X-API-KEY
      credentials:
        - key: ${API_KEY_CATALOG_SERVICE:default-api-key}
          enabled: true
          roles: [ ADMIN ]

  errors:
    CATALOG_NOT_FOUND: 404

redisson:
  address: ${REDIS_ADDRESS:redis://localhost:6379}
  database: ${REDIS_DATABASE:0}

Example declarative client usage

A generated client such as ProductApi can be injected like any other Spring bean and used directly from controllers or services.

catalog-service calling ProductApi

@RestController
@RequestMapping("/api/v1/catalogs")
public class CatalogController {

    private final ProductApi productApi;

    public CatalogController(ProductApi productApi) {
        this.productApi = productApi;
    }

    @GetMapping("/{id}/product/{productId}")
    public ProductDto getProductFromCatalog(@PathVariable UUID id, @PathVariable UUID productId) {
        return productApi.getProduct(productId);
    }
}

Build and run

The whole project can be built from the repository root, and each bootable module can then be run independently.

Build from root

mvn -T 1C -DskipTests clean package

Run catalog-service

java -jar target/catalog-service-boot-<version>.jar

What to adapt for your own service

The example is intentionally concrete, but it is still a starting point. Most real services will adapt only the parts they actually need.

Trim unused modules

Keep only the Spring Middleware modules that your runtime concern actually needs.

Adjust configuration

Tailor security, messaging, logging, and registry settings to your environment.

Keep the structure explicit

Use the example as a reference, but keep your own service boundaries and runtime choices explicit.