Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
- changed-files:
- any-glob-to-any-file: 'spring-batch-file-examples/**/*'

"project: spring-caffeine-example":
- changed-files:
- any-glob-to-any-file: 'spring-caffeine-example/**/*'

"project: springwolf-example":
- changed-files:
- any-glob-to-any-file: 'spring-wolf-example/**/*'
Expand Down
68 changes: 68 additions & 0 deletions .github/workflows/spring-caffeine-example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: spring-caffeine-example CI Build

on:
pull_request:
branches: [master]
paths:
- "spring-caffeine-example/**"
types:
- opened
- synchronize
- reopened

jobs:

integration-tests:
name: Run Unit & Integration Tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: spring-caffeine-example
strategy:
matrix:
distribution: [ 'temurin' ]
java: [ '21' ]
Comment on lines +23 to +24
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Fix bracket spacing for YAML lint compliance.

YAMLlint reports extra spaces inside brackets on these lines.

Apply this diff:

-        distribution: [ 'temurin' ]
-        java: [ '21' ]
+        distribution: ['temurin']
+        java: ['21']
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
distribution: [ 'temurin' ]
java: [ '21' ]
distribution: ['temurin']
java: ['21']
🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 23-23: too many spaces inside brackets

(brackets)


[error] 23-23: too many spaces inside brackets

(brackets)


[error] 24-24: too many spaces inside brackets

(brackets)


[error] 24-24: too many spaces inside brackets

(brackets)

🤖 Prompt for AI Agents
.github/workflows/spring-caffeine-example.yml lines 23-24: the array brackets
contain extra spaces which fails YAML lint; remove the spaces inside the
brackets so the arrays use compact form (e.g. change "distribution: [ 'temurin'
]" to "distribution: ['temurin']" and "java: [ '21' ]" to "java: ['21']").

steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v5.1.0
with:
java-version: ${{ matrix.java }}
distribution: ${{ matrix.distribution }}
cache: 'maven'
- name: Build and analyze
run: ./mvnw clean verify

health-check:
name: Health Check on Services
runs-on: ubuntu-latest
steps:
- name: Checkout repository and submodules
uses: actions/checkout@v6
with:
submodules: true

- name: Extract service names from docker compose
id: services
run: |
echo "services<<EOF" >> $GITHUB_OUTPUT
docker compose -f ./spring-caffeine-example/compose.yaml config --services >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT

- name: Start containers with Compose Action
uses: hoverkraft-tech/compose-action@v2.4.2
with:
compose-file: './spring-caffeine-example/compose.yaml'
services: ${{ steps.services.outputs.services }}
up-flags: '--build'
down-flags: '--volumes'

- name: Wait for containers to initialize
run: sleep 10

- name: Check container health
run: |
./.github/scripts/check-container-health.sh "${{ steps.services.outputs.services }}"
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Below you will find a summary table of each subproject. For more details, please
| [Spring Boot + Mysql](./spring-mysql-example) | Demonstrates a basic integration between Spring Boot and Mysql. |
| [Spring Boot + Grafana + Prometheus](./spring-prometheus-grafana-example) | Demonstrates a basic integration between Spring Boot With Grafana and Prometheus. |
| [Spring Boot + Spring Wolf](./spring-wolf-example) | Demonstrates a basic integration between Spring Boot and Spring Wolf. |
| [Spring Boot + Caffeine](./spring-caffeine-example) | Demonstrates a basic integration between Spring Boot and Caffeine. |
---

## Tech Stack
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<module>spring-batch-file-examples</module>
<module>spring-batch-db-examples</module>
<module>spring-wolf-example</module>
<module>spring-caffeine-example</module>
</modules>

</project>
62 changes: 62 additions & 0 deletions spring-caffeine-example/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Include any files or directories that you don't want to be copied to your
# container here (e.g., local build artifacts, temporary files, etc.).
#
# For more help, visit the .dockerignore file reference guide at
# https://docs.docker.com/go/build-context-dockerignore/

**/.DS_Store
**/.classpath
**/.dockerignore
**/.env
**/.factorypath
**/.git
**/.gitignore
**/.idea
**/.project
**/.sts4-cache
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.next
**/.cache
**/*.dbmdl
**/*.jfm
**/charts
**/docker-compose*
**/compose.y*ml
**/Dockerfile*
**/secrets.dev.yaml
**/values.dev.yaml
**/vendor
LICENSE
README.md
**/*.class
**/*.iml
**/*.ipr
**/*.iws
**/*.log
**/.apt_generated
**/.gradle
**/.gradletasknamecache
**/.nb-gradle
**/.springBeans
**/build
**/dist
**/gradle-app.setting
**/nbbuild
**/nbdist
**/nbproject/private
**/target
*.ctxt
.mtj.tmp
.mvn/timing.properties
buildNumber.properties
dependency-reduced-pom.xml
hs_err_pid*
pom.xml.next
pom.xml.releaseBackup
pom.xml.tag
pom.xml.versionsBackup
release.properties
replay_pid*
2 changes: 2 additions & 0 deletions spring-caffeine-example/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf
30 changes: 30 additions & 0 deletions spring-caffeine-example/.github/workflows/maven.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: CI Build

on:
push:
branches:
- "**"

jobs:
build:
name: Build
runs-on: ubuntu-latest
strategy:
matrix:
distribution: [ 'temurin' ]
java: [ '21' ]
steps:
- uses: actions/checkout@v6

- name: Setup Java 21
uses: actions/setup-java@v5
with:
java-version: ${{ matrix.java }}
distribution: ${{ matrix.distribution }}
cache: 'maven'

- name: Grant execute permission for mvnw
run: chmod +x mvnw

- name: Build with Maven
run: ./mvnw clean verify
32 changes: 32 additions & 0 deletions spring-caffeine-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
target/
.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/

### VS Code ###
.vscode/
3 changes: 3 additions & 0 deletions spring-caffeine-example/.mvn/wrapper/maven-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
49 changes: 49 additions & 0 deletions spring-caffeine-example/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
FROM eclipse-temurin:21-jdk-jammy as deps

WORKDIR /build

COPY --chmod=0755 mvnw mvnw
COPY .mvn/ .mvn/

RUN --mount=type=bind,source=pom.xml,target=pom.xml \
--mount=type=cache,target=/root/.m2 ./mvnw dependency:go-offline -DskipTests

FROM deps as package

WORKDIR /build

COPY ./src src/
RUN --mount=type=bind,source=pom.xml,target=pom.xml \
--mount=type=cache,target=/root/.m2 \
./mvnw package -DskipTests && \
mv target/$(./mvnw help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout).jar target/app.jar
Comment on lines +16 to +19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix shell quoting to prevent word splitting.

Line 19 uses unquoted command substitutions that could cause word splitting issues if the artifact ID or version contains spaces or special characters.

Apply this diff:

     ./mvnw package -DskipTests && \
-    mv target/$(./mvnw help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout).jar target/app.jar
+    mv "target/$(./mvnw help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout).jar" target/app.jar
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RUN --mount=type=bind,source=pom.xml,target=pom.xml \
--mount=type=cache,target=/root/.m2 \
./mvnw package -DskipTests && \
mv target/$(./mvnw help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout).jar target/app.jar
RUN --mount=type=bind,source=pom.xml,target=pom.xml \
--mount=type=cache,target=/root/.m2 \
./mvnw package -DskipTests && \
mv "target/$(./mvnw help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout).jar" target/app.jar
🧰 Tools
🪛 Hadolint (2.14.0)

[warning] 16-16: Quote this to prevent word splitting.

(SC2046)

🤖 Prompt for AI Agents
In spring-caffeine-example/Dockerfile around lines 16 to 19, the mv command uses
unquoted command substitutions which can lead to word splitting if artifactId or
version contain spaces or special characters; update the Dockerfile to capture
the two evaluated values into variables or directly quote the command
substitution expansions so the resulting filename is treated as a single
argument (e.g., wrap each $(...) expansion in double quotes or assign them to
variables and then reference the variables quoted) and ensure the mv source and
destination arguments are properly quoted to prevent splitting and unexpected
behavior.


FROM package as extract

WORKDIR /build

RUN java -Djarmode=layertools -jar target/app.jar extract --destination target/extracted


FROM eclipse-temurin:21-jre-jammy AS final


ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
appuser
USER appuser

COPY --from=extract build/target/extracted/dependencies/ ./
COPY --from=extract build/target/extracted/spring-boot-loader/ ./
COPY --from=extract build/target/extracted/snapshot-dependencies/ ./
COPY --from=extract build/target/extracted/application/ ./

Comment on lines +28 to +46
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add WORKDIR and HEALTHCHECK for production readiness.

The final stage has two issues:

  1. Missing WORKDIR before COPY commands (lines 42-45), which means files are copied to the root directory
  2. Missing HEALTHCHECK instruction for container health monitoring

Apply this diff:

 FROM eclipse-temurin:21-jre-jammy AS final
 
+WORKDIR /app
 
 ARG UID=10001
 RUN adduser \
     --disabled-password \
     --gecos "" \
     --home "/nonexistent" \
     --shell "/sbin/nologin" \
     --no-create-home \
     --uid "${UID}" \
     appuser
 USER appuser
 
 COPY --from=extract build/target/extracted/dependencies/ ./
 COPY --from=extract build/target/extracted/spring-boot-loader/ ./
 COPY --from=extract build/target/extracted/snapshot-dependencies/ ./
 COPY --from=extract build/target/extracted/application/ ./
 
 EXPOSE 7079
 
+HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
+  CMD java -cp . org.springframework.boot.actuate.health.HealthEndpoint || exit 1
+
 ENTRYPOINT [ "java", "org.springframework.boot.loader.launch.JarLauncher" ]

Note: For the HEALTHCHECK to work properly, you'll need Spring Boot Actuator. If not using Actuator, consider a simple HTTP health check using curl or wget.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Hadolint (2.14.0)

[warning] 42-42: COPY to a relative destination without WORKDIR set.

(DL3045)


[warning] 43-43: COPY to a relative destination without WORKDIR set.

(DL3045)


[warning] 44-44: COPY to a relative destination without WORKDIR set.

(DL3045)


[warning] 45-45: COPY to a relative destination without WORKDIR set.

(DL3045)

🤖 Prompt for AI Agents
In spring-caffeine-example/Dockerfile around lines 28 to 46, the final stage is
missing a WORKDIR before the COPYs and lacks a HEALTHCHECK; add a WORKDIR (e.g.
/app) before the COPY lines and ensure the directory exists/has correct
ownership (create and chown if necessary or set before switching to the
unprivileged USER), then add a HEALTHCHECK that probes your app (preferably
Spring Boot Actuator /actuator/health on the container port) with sensible
interval/timeout/retries or a simple curl/wget HTTP check if Actuator is not
available; keep the USER placement and permissions consistent so the health
probe can run and files are placed under the intended app directory.

EXPOSE 7079

ENTRYPOINT [ "java", "org.springframework.boot.loader.launch.JarLauncher" ]
29 changes: 29 additions & 0 deletions spring-caffeine-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# spring-caffeine-example

## Overview

This project is a simple and practical example demonstrating how to use **Caffeine** as an in-memory cache in a Spring Boot application.

The main goal is to show how Caffeine can be used to cache:
- external service calls (e.g. HTTP/Feign clients)
- database lookups

The project focuses on using **Caffeine directly**, avoiding unnecessary abstractions, to achieve better performance, predictability, and simplicity.

---

## Main Endpoint

| Environment | Curl |
|-------------|-------------------------------------------------------------|
| Local | curl --location 'http://localhost:8080/caffeine/v1/request' |
| Docker | curl --location 'http://localhost:7079/caffeine/v1/request' |

---

## Related Resources

| Description | Link |
|--------------------------|------------------------------------------------|
| Caffeine Github | https://github.com/ben-manes/caffeine |
| Introduction to Caffeine | https://www.baeldung.com/java-caching-caffeine |
23 changes: 23 additions & 0 deletions spring-caffeine-example/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
services:

app:
container_name: app
build:
context: .
environment:
SPRING_PROFILES_ACTIVE: prd
SERVER_PORT: 7079
H2_DB_URL: jdbc:h2:mem:caffeine-db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
H2_DB_DRIVER: org.h2.Driver
H2_DB_USERNAME: sa
H2_DB_PASSWORD: ""
H2_CONSOLE_ENABLED: false
H2_CONSOLE_PATH: /h2-console
H2_INIT_MODE: always
H2_INIT_SCHEMA: classpath:sql/init.sql
CAFFEINE_VERSION_URL: localhost:7079/external
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check Feign client configuration and URL usage

# Find Feign client configurations
echo "=== Searching for FeignClient declarations ==="
rg -n "FeignClient\|@FeignClient" --type java -A 3

echo ""
echo "=== Searching for caffeine-version-url property usage ==="
rg -n "caffeine-version-url\|CAFFEINE_VERSION_URL" --type yaml --type java --type properties

echo ""
echo "=== Checking compose.yaml for all environment variables ==="
cat spring-caffeine-example/compose.yaml

Repository: igorcampos-dev/spring-integrations-examples

Length of output: 936


🏁 Script executed:

#!/bin/bash
# Find files in spring-caffeine-example module
echo "=== Files in spring-caffeine-example ==="
find spring-caffeine-example -type f -name "*.java" -o -name "*.properties" -o -name "*.yml" | head -20

echo ""
echo "=== Search for Feign dependency ==="
rg -l "feign\|Feign" spring-caffeine-example

echo ""
echo "=== Search for property usage with broader patterns ==="
rg -n "CAFFEINE_VERSION\|caffeine" spring-caffeine-example --type java -i

echo ""
echo "=== Check if this is a client calling an external service ==="
rg -n "@RestClient\|@FeignClient\|RestTemplate\|WebClient" spring-caffeine-example --type java -i

Repository: igorcampos-dev/spring-integrations-examples

Length of output: 1372


🏁 Script executed:

#!/bin/bash
echo "=== CaffeineVersionClient.java ==="
cat spring-caffeine-example/src/main/java/com/io/example/client/CaffeineVersionClient.java

echo ""
echo "=== CaffeineProperties.java ==="
cat spring-caffeine-example/src/main/java/com/io/example/properties/CaffeineProperties.java

echo ""
echo "=== Check application properties files ==="
find spring-caffeine-example/src -name "application*.properties" -o -name "application*.yml" | xargs cat

Repository: igorcampos-dev/spring-integrations-examples

Length of output: 1014


🏁 Script executed:

#!/bin/bash
echo "=== Search for external.caffeine-version-url references ==="
rg -n "external.caffeine-version-url\|external\.caffeine" spring-caffeine-example

echo ""
echo "=== Check for all application config files ==="
find spring-caffeine-example -type f \( -name "*.properties" -o -name "*.yml" -o -name "*.yaml" \) | grep -v ".mvn\|wrapper\|workflows"

echo ""
echo "=== Check Dockerfile for property configuration ==="
cat spring-caffeine-example/Dockerfile

echo ""
echo "=== Check pom.xml for spring-cloud-openfeign ==="
grep -A 5 -B 5 "feign\|openfeign" spring-caffeine-example/pom.xml

Repository: igorcampos-dev/spring-integrations-examples

Length of output: 2574


🏁 Script executed:

#!/bin/bash
echo "=== application.yaml ==="
cat spring-caffeine-example/src/main/resources/application.yaml

echo ""
echo "=== application-prd.yaml ==="
cat spring-caffeine-example/src/main/resources/application-prd.yaml

Repository: igorcampos-dev/spring-integrations-examples

Length of output: 1282


The CAFFEINE_VERSION_URL is missing the protocol (http:// or https://), which will cause Feign client connection failures.

Both environment configurations are affected:

  • Local: localhost:8080/external in application.yaml
  • Production: localhost:7079/external in compose.yaml (mapped via application-prd.yaml)

Update the URL to include the protocol: http://localhost:7079/external

🤖 Prompt for AI Agents
In spring-caffeine-example/compose.yaml around line 18, the CAFFEINE_VERSION_URL
value lacks an HTTP scheme which will break Feign client calls; update the
environment value to include the protocol (e.g. change to
"http://localhost:7079/external") so the service has a fully qualified URL, and
ensure any other environment configs (local/production) are likewise updated to
include "http://" or "https://" as appropriate.

CAFFEINE_EXPIRES: 60 #seconds
CAFFEINE_CAPACITY: 200
CAFFEINE_MAXIMUM_SIZE: 500
ports:
- "7079:7079"
Loading