Skip to content

Feature/overall enhancements - #1

Merged
SaumilP merged 42 commits into
mainfrom
feature/overall-enhancements
Jul 5, 2026
Merged

SaumilP merged 42 commits into
mainfrom
feature/overall-enhancements

Conversation

@SaumilP

@SaumilP SaumilP commented Jul 4, 2026 •

Copy link
Copy Markdown
Owner

Summary

This PR completes a full-scale overhaul of the spring-boot-starters mono-repo, adding nine new Spring Boot starters, ten runnable example sub-modules, Testcontainers integration tests, Maven Central publishing config, GitHub Actions CI/CD pipelines, and a migration of the entire Gradle build from Groovy DSL to Kotlin DSL.


What Changed

Build System

  • Groovy → Kotlin DSL: All 23 build.gradle files (root, settings.gradle, 11 starters, 10 examples) converted to build.gradle.kts / settings.gradle.kts
  • Root build.gradle.kts uses configure<> extension accessors, tasks.named, tasks.withType, and proper Kotlin DSL signing/publishing blocks
  • Spotless target updated from *.gradle to *.kts

Package & Namespace

  • All source packages migrated from org.sandcastle.starters → io.github.saumilp.starters
  • Example app packages corrected from io.github.saumilp.examples → io.github.saumilp.starters.examples
  • Maven group ID: io.github.saumilp.starters

New Starters (9 added)

┌───────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────┐
│              Module               │                                            Key capability                                             │
├───────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ spring-boot-starter-rate-limiting │ @RateLimit AOP annotation, sliding-window in-memory + Redis token-bucket backends, configurable       │
│                                   │ per-key strategy                                                                                      │
├───────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ spring-boot-starter-idempotency   │ Servlet filter on Idempotency-Key header, Redis-backed response cache, concurrent-duplicate 409 guard │
├───────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ spring-boot-starter-audit-log     │ @Audited AOP annotation, pluggable AuditEventSink (logging / JPA / composite), Spring Security        │
│                                   │ ActorResolver                                                                                         │
├───────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ spring-boot-starter-feature-flags │ OpenFeature SDK integration, @FeatureEnabled AOP gate, YAML file-based provider                       │
├───────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ spring-boot-starter-llm-client    │ OpenAI-compatible nt, configurable retry, Micrometer metrics                                          │
├───────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ spring-boot-starter-multitenancy  │ TenantContext threr-tenant ConnectionProvider, header + subdomain                                     │
│                                   │ TenantResolver                                                                                        │
├───────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ spring-boot-starter-aws-s3        │ AWS SDK v2 S3StorageService, pre-signed URL generation, S3HealthIndicator, LocalStack-compatible      │
│                                   │ endpoint override                                                                                     │
├───────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ spring-boot-starter-outbox        │ Transactional Outbentity, @Scheduled relay, Kafka + RabbitMQ                                          │
│                                   │ MessageBrokerAdapter                                                                                  │
├───────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ spring-boot-starter-common        │ Shared @StarterBean, StarterException hierarchy, HealthDetails builder, MeterRegistryUtils            │
└───────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────┘

Existing Starters Enhanced

  • spring-boot-starter-redis: Added RedisHealthIndicator, RedisLockUtil, Testcontainers integration tests
  • spring-boot-starter-minio: Added MinioHealthIndicator, MinioMetricsConfiguration, extended StorageService API, Testcontainers integration tests

Example Applications (10 added)

Each example lives under examples/-example/ and sh

  • A runnable Spring Boot application wired to the corresponding starter
  • docker-compose.yml for its infrastructure dependencies
  • Per-example README.md with usage instructions

Examples: redis, minio, rate-limiting, idempotency, audit-log, feature-flags, llm-client, multitenancy, aws-s3, outbox

CI/CD & Publishing

  • .github/workflows/ci.yml — build + test on every push/PR
  • .github/workflows/release.yml — publish to Maven Centrsh using in-memory GPG signing
  • .github/CODEOWNERS and PULL_REQUEST_TEMPLATE.md added

Documentation

  • Root README.md rewritten with starter matrix, quick-start, and configuration reference
  • Per-starter and per-example README.md files added (11
  • CONTRIBUTING.md and CHANGELOG.md added

Tech Stack

  • Spring Boot 4.0.4 / Spring Framework 7.x / Jakarta EE
  • Gradle 8.x (Kotlin DSL)
  • OpenFeature SDK 1.11, AWS SDK v2 2.26, Testcontainers,

Test Plan

  • ./gradlew build passes on all 21 sub-modules
  • ./gradlew test passes; integration tests require Dis, MinIO)
  • Spotless check: ./gradlew spotlessCheck
  • Example apps start cleanly with docker-compose up es
  • Signing/publishing: ./gradlew publishToMavenLocal succeeds without GPG env vars (signing skipped gracefully)

Saumil Patel and others added 27 commits February 10, 2025 23:48
…isation

Phase 0 – Repository restructure:
- Add root build.gradle (Spring Boot 4.0.4 BOM, Spotless 7.0.3, Java 21 toolchain,
  Maven Central/GPG publication, javadoc gate)
- Add root settings.gradle with multi-module include for common, minio, redis
- Remove per-module settings.gradle files (spring-boot-starter-minio,
  spring-boot-starter-redis) — conflicts with root multi-module build
- Update spring-boot-starter-minio/build.gradle: BOM-managed deps, common module
  via `api project(':spring-boot-starter-common')`
- Update spring-boot-starter-redis/build.gradle: same pattern, Java 21 toolchain,
  jackson modules added, actuator/aop/aspectj as compileOnly

Phase 0 – spring-boot-starter-common module (new):
- StarterException / StarterConfigurationException — shared unchecked exception hierarchy
- StarterBean — informational marker annotation
- MeterRegistryUtils — Micrometer tag/status constants
- HealthDetails — fluent builder for actuator detail maps (null-safe, copy-on-return)
- Unit tests: StarterExceptionTest, HealthDetailsTest

Phase 1 – Redis starter modernisation:
- Replace @configuration with @autoConfiguration + @ConditionalOnClass(RedisConnectionFactory)
- Add META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
- Remove deprecated META-INF/spring.factories
- Remove CachingConfigurerSupport (deleted in Spring Framework 7.x); use CachingConfigurer
- All auto-configured beans guarded with @ConditionalOnMissingBean for consumer override
- Extract buildObjectMapper() private helper — eliminates three duplicate ObjectMapper builds
- RedisConfigurationProperties: add host, port, metricName, cacheTtlDays with full Javadoc
- Add RedisOperationException extending StarterException (replaces misnamed MinioException files)
- Delete MinioException / MinioFetchException from wrong module (redis)
- Add RedisHealthIndicator (actuator HealthIndicator, PING-based check)
  - Conditionally registered via @ConditionalOnProperty
    management.health.redis-custom.enabled (default true)
  - Uses HealthDetails builder from spring-boot-starter-common
  - Unit tests: 4 tests covering UP/DOWN paths and detail-map content

- Add RedisMetricsAspect (AOP @around on all RedisUtil public methods)
  - Wraps each call in a Micrometer Timer tagged with operation + status
  - Metric name driven by RedisConfigurationProperties.metricName (default: redis.operations)
  - Unit tests: 4 tests covering success timer, error timer, operation tag, custom metric name

- Wire both beans into RedisAutoConfiguration
  - Health indicator: @ConditionalOnClass(HealthIndicator.class) + @ConditionalOnMissingBean
  - Metrics aspect: @ConditionalOnClass(MeterRegistry) + @ConditionalOnBean(MeterRegistry.class)
  - Both remain opt-out via @ConditionalOnMissingBean for consumer override

- Update redis build.gradle: add actuator, micrometer-core, aspectjweaver as testImplementation
Redis integration tests (tagged 'integration', use redis:7.2-alpine container):
- RedisUtilIntegrationTest: store/retrieve, TTL expiry, Duration-based TTL, delete
- RedisLockUtilIntegrationTest: acquire, second-acquire blocked, release then reacquire,
  wrong-requestId cannot release another owner's lock (Lua atomicity verified)
- RedisHealthIndicatorIntegrationTest: UP status, PONG response detail, component tag

MinIO integration tests (tagged 'integration', use MinIOContainer via testcontainers-minio):
- MinioStorageServiceIntegrationTest: bucket auto-create, upload + list, remove object,
  list buckets

Build changes:
- spring-boot-starter-redis/build.gradle: add spring-boot-testcontainers,
  testcontainers:junit-jupiter as testImplementation
- spring-boot-starter-minio/build.gradle: add spring-boot-testcontainers,
  testcontainers:junit-jupiter, testcontainers:minio as testImplementation
… CI/CD workflows

Phase 4 — Maven Central publishing (already in root build.gradle from Phase 0):
- OSSRH repository URL toggled on SNAPSHOT vs release via version suffix
- In-memory GPG signing guarded on GPG_PRIVATE_KEY presence (no-op locally)
- Both mavenJava publications include sources + javadoc jars

Phase 5 — GitHub Actions:
- .github/workflows/ci.yml: matrix build (Java 21), unit tests, javadoc gate,
  Spotless lint check, integration-tests job (Testcontainers, tagged 'integration')
- .github/workflows/release.yml: triggered on v*.*.* tags, publishes to OSSRH
  via OSSRH_USERNAME/PASSWORD + GPG_PRIVATE_KEY/GPG_PASSPHRASE secrets,
  creates GitHub Release with auto-generated notes
- .github/CODEOWNERS: default owner @saumilpatel
- .github/PULL_REQUEST_TEMPLATE.md: checklist covering tests, JavaDoc, Spotless, CHANGELOG
…NG, CHANGELOG

Root README:
- Project overview, badge strip (CI, Maven Central, License, Java 21, Spring Boot 4.x)
- Starter table with links and versions
- Quick-start snippets (Gradle + Maven)
- Design philosophy (consumer-first, no schema mgmt, observable by default, Testcontainers)
- Project structure tree, build commands

spring-boot-starter-redis/README.md:
- Full property reference table (host, port, metric-name, cache-ttl-days)
- Auto-configured beans table with @ConditionalOnMissingBean notes
- RedisUtil usage example with key method signatures
- RedisLockUtil usage example with Lua script explanation
- Spring Cache integration with key generator format
- Health indicator JSON output + disable snippet
- Micrometer Prometheus output sample
- Override pattern example

spring-boot-starter-minio/README.md:
- Full property reference table
- StorageService API table and usage example
- Health indicator + metrics sections
- Proxy support documentation
- Docker quick-start snippet

spring-boot-starter-common/README.md:
- Exception hierarchy, annotation, MeterRegistryUtils constants, HealthDetails usage

CONTRIBUTING.md:
- Java 21 coding standards, Spring auto-configuration patterns
- JavaDoc format requirements (enforced by CI javadoc task)
- Unit test + integration test conventions
- New starter checklist
- Conventional Commits format
- Release process

CHANGELOG.md:
- [Unreleased] section documenting all Phase 0–6 changes
- [2.0.0] minio + [1.0.0] redis historical entries
- Root settings.gradle + build.gradle (Spring Boot 4.0.4, Java 21, shared Spotless/signing/publication config)
- Full spring-boot-starter-common module: StarterException, StarterConfigurationException, StarterBean, MeterRegistryUtils, HealthDetails — all with professional Javadoc + unit tests
- Updated both existing build.gradle files to Spring Boot 4.0.4 with api dep on common
- Removed standalone settings.gradle from each subproject
- Deletes MinioException.java + MinioFetchException.java (wrong package, wrong name)
- Creates RedisOperationException extends StarterException
- Deletes deprecated spring.factories → creates AutoConfiguration.imports
- Rewrites RedisKeyGenerator to implements CachingConfigurer (Spring Framework 7.x compat)
- Rewrites RedisConfigurationProperties with host, port, metricName, cacheTtlDays fields
- Rewrites RedisAutoConfiguration with @autoConfiguration, @ConditionalOnMissingBean on all beans, extracted buildObjectMapper(), TTL from properties
…, Redis+in-memory backends

- @ratelimit annotation (method/class level) with requests, per (TimeUnit), key, name attributes
- RateLimiter interface with tryConsume(key, maxRequests, windowSeconds)
- RedisTokenBucketRateLimiter: atomic Lua sliding-window script over sorted set, fail-open on Redis error
- InMemorySlidingWindowRateLimiter: ConcurrentHashMap + synchronized Deque<Long>, JVM-local fallback
- RateLimitAspect: @around AOP, resolves method/class annotation, named-limit lookup, IP+method default key
- RateLimitExceptionHandler: HTTP 429 with Retry-After header and structured JSON body
- RateLimitAutoConfiguration: @ConditionalOnMissingBean wiring, Redis preferred over in-memory
- RateLimitProperties: spring.rate-limit.* with named-limits map
- Unit tests: InMemorySlidingWindowRateLimiterTest (bucket isolation, expiry), RateLimitExceededExceptionTest
- Comprehensive README with config table, usage examples, observability section
- Package update: io.github.saumilp.starters
- Maven group update : io.github.saumilp.starters
- GitHub Repo update: github.com/SaumilP
- Email update: email2saumil2024@gmail.com
- CODEOWNERS handle update: @SaumilP

All Java source trees moved to new directory layout.
All imports, AutoConfiguration.imports, and spring.factories updated.
… per starter

Adds a complete examples/ directory with one runnable Spring Boot 4 application per
starter. Each sub-module includes: build.gradle, application class, controller/service,
application.yml, docker-compose.yml for local infrastructure, and README.md.

Examples: redis, minio, rate-limiting, idempotency, audit-log, feature-flags,
llm-client, multitenancy, aws-s3 (LocalStack), outbox (PostgreSQL + Kafka).
Top-level examples/README.md cross-references all examples with infra requirements.
…e.kts)

Replaces all 23 Groovy DSL build files with Kotlin DSL equivalents:
- settings.gradle → settings.gradle.kts
- build.gradle (root) → build.gradle.kts
- 11 starter module build.gradle → build.gradle.kts
- 10 example sub-module build.gradle → build.gradle.kts
@SaumilP SaumilP self-assigned this Jul 4, 2026
@SaumilP SaumilP added documentation Improvements or additions to documentation enhancement New feature or request labels Jul 4, 2026
SaumilP added 15 commits July 4, 2026 22:45
- Move gradlew/gradlew.bat from spring-boot-starter-redis/ to project root
- Add gradle/wrapper/gradle-wrapper.jar + gradle-wrapper.properties (Gradle 8.14.3)
  — Spring Boot 4.0.4 requires Gradle 8.14+
- Unblock gradle-wrapper.jar from .gitignore (*.jar negation)
- Remove core plugins (java-library, maven-publish, signing) from root plugins {}
  block — core plugins cannot use apply false in Kotlin DSL
- Add Foojay toolchain resolver to settings.gradle.kts for JDK 21 auto-provisioning
- Fix CI cache key hash pattern: **/*.gradle → **/*.gradle.kts
- Remove invalid docker:dind service from integration-tests job (Docker is
  pre-installed on ubuntu-latest; Testcontainers uses /var/run/docker.sock)
- Simplify integration-tests run command to target only redis and minio modules
Replace io.spring.dependency-management BOM import (configure<> inside
subprojects{} is unreliable in Kotlin DSL when plugin is applied dynamically)
with Gradle-native platform() BOM on api/annotationProcessor/testImplementation/
testRuntimeOnly configurations in the starter subprojects block.

Examples are unchanged — they apply io.spring.dependency-management in their
own plugins block alongside org.springframework.boot, which auto-imports the BOM.

Also replace the minio starter's custom provided configuration with compileOnly
so spring-aop and micrometer-core get their versions resolved through the BOM.
Spring Boot 4.0 removed/restructured several APIs that required updates:

Health API (spring-boot-health 4.0):
- HealthIndicator, Health, Status moved from org.springframework.boot.actuate.health
  to org.springframework.boot.health.contributor
- ConditionalOnEnabledHealthIndicator, HealthContributorAutoConfiguration moved
  to org.springframework.boot.health.autoconfigure.contributor

AOP starter removed:
- spring-boot-starter-aop no longer exists in Spring Boot 4.0
- Replaced with direct dependencies: spring-aop + aspectjweaver
- Affected: audit-log, feature-flags, rate-limiting starters and their examples

Jackson 3.x migration (tools.jackson):
- RedisIdempotencyStore: ObjectMapper/JacksonException now from tools.jackson
- RedisAutoConfiguration: add explicit jackson-databind 2.x dep (needed by
  Spring Data Redis Jackson2JsonRedisSerializer legacy API)
- RedisUtil: remove internal org.springframework.boot.configurationprocessor.json
  usage (dead code that built an unused JSONObject)
- MinioStorageServiceImpl: remove com.sun.java.accessibility.util.EventID hack,
  replace with literal 0; drop unused jackson import

Example fixes:
- S3Controller: fix method signatures to match actual S3StorageService interface
- BetaController: add explicit dev.openfeature:sdk dep (not exported by starter)
- CacheController: fix del() method name (not delete())
- LlmClientAutoConfiguration: fix @PARAM name mismatch in javadoc
… 2.x

Testcontainers 2.0 changes:
- Artifact IDs gained a testcontainers- prefix: junit-jupiter ->
  testcontainers-junit-jupiter, minio -> testcontainers-minio
- @testcontainers(disabledWithoutDocker=true) replaced by the new
  @EnabledIfDockerAvailable annotation; applied to all four integration tests
  so they skip gracefully when Docker is unavailable

Spring Boot 4.0 autoconfigure relocation:
- RedisAutoConfiguration renamed to DataRedisAutoConfiguration and moved to
  org.springframework.boot.data.redis.autoconfigure (spring-boot-data-redis)
- Updated all three Redis integration tests

Other test fixes:
- multitenancy: add spring-boot-starter-web to testImplementation so
  jakarta.servlet HttpServletRequest is on the test classpath
- outbox: add public setCreatedAt to OutboxEvent (only field lacking a setter)
  and update OutboxEventRelayTest to build fixtures via setters instead of the
  protected onCreate() JPA lifecycle callback (inaccessible cross-package)
…vadoc

CI unit-test fix:
- The "Run unit tests" step ran ./gradlew test, which executed the
  @tag("integration") Testcontainers tests. Docker IS available in CI, so
  @EnabledIfDockerAvailable did not skip them, and the MinIO container failed
  to pull (pinned image tag RELEASE.2024-01-01T00-00-00Z no longer exists ->
  NotFoundException).
- The root `test` task now excludes the "integration" tag; a dedicated
  `integrationTest` task runs only integration-tagged tests. CI's integration
  job now calls :spring-boot-starter-redis:integrationTest and
  :spring-boot-starter-minio:integrationTest.
- Updated the MinIO test image to a valid tag (RELEASE.2025-07-23T15-54-02Z).

Flaky unit test fix:
- HealthDetails.build() returned Map.copyOf(), which has no iteration-order
  guarantee, so HealthDetailsTest.should_preserveInsertionOrder was
  order-dependent. Now returns an unmodifiable LinkedHashMap copy, preserving
  the insertion order the class (and test) intends.

Javadoc:
- Documented the public API across all library modules: interface methods and
  params (StorageService), configuration-property accessors, exception and
  auto-configuration constructors, health indicators, and annotation elements.
  Fixes all ~300 missing-doc warnings.
- Example apps are demonstration code and not published, so their Javadoc task
  is disabled.
- RedisUtil/RedisLockUtil (internal helpers over the deprecated byte[]
  RedisConnection API) are excluded from generated Javadoc; runtime code is
  untouched. Remaining warnings are third-party deprecation/unchecked/removal
  notes the Javadoc tool surfaces from its compile pass and cannot be told to
  suppress. Javadoc now builds cleanly (0 doc warnings, down from 338).
The @container is static (shared across all tests in the class) and every test
used the same LOCK_KEY. The @AfterEach cleanup called
releaseLock(LOCK_KEY, "cleanup-id"), but releaseLock only deletes the key when
the stored value matches the requestId — its by-design safety semantics. Since
"cleanup-id" never matches the actual holder (e.g. "request-1"), the cleanup was
a no-op and the lock leaked between tests with its TTL still active.

As a result should_acquireLock_when_keyIsAvailable failed whenever it ran after
a test that left the lock held (e.g. should_rejectSecondAcquire), because
SET_IF_ABSENT could not acquire the still-present key. Cleanup now deletes the
key unconditionally so each test starts from a clean state.
MinioAutoConfiguration created the properties with a plain @bean returning
`new MinioConfigurationProperties()` and relied on the ConfigurationProperties
binding post-processor (registered by ConfigurationPropertiesAutoConfiguration)
to populate it. In the integration test's minimal context
(@SpringBootTest(classes = MinioAutoConfiguration.class)) that auto-config is
not active, so `spring.minio.*` never bound and getUrl() returned null.
MinioClient.builder().endpoint(null) then threw IllegalArgumentException in
HttpUtils.validateNotNull (HttpUtils.java:84), failing the application context
and all four tests.

Switch to the standard pattern: @EnableConfigurationProperties on the
auto-configuration (which registers the binding infrastructure and a single
bound bean), inject MinioConfigurationProperties into minioClient(), and drop
the unbound @bean factory method. Also removed the erroneous self-referential
@EnableConfigurationProperties from the properties class. Behaviour is
unchanged for full Spring Boot applications and now also correct in the
minimal test context.
The test built the object path as Path.of(TEST_BUCKET, TEST_OBJECT), but the
StorageService.upload(Path, ...) overload treats the Path as the object key
within the default bucket (it already targets clientProps.getBucket()). The
key therefore became "integration-test-bucket/test/hello.txt" while the
assertion looked for "test/hello.txt", failing
should_uploadAndRetrieveObject_when_fileUploaded at line 92.

Use Path.of(TEST_OBJECT) so the object key matches the expected value.
listObjectNames() lists via a non-recursive ListObjectsArgs (recursive defaults
to false), so MinIO groups a nested key like "test/hello.txt" under the common
prefix "test/" and returns that pseudo-directory instead of the full key. The
upload succeeded but the assertion contains("test/hello.txt") failed because the
listing returned "test/".

Use a flat key ("hello.txt") that appears directly in the non-recursive listing,
matching the implementation's documented behaviour.
@SaumilP
SaumilP merged commit 31fc588 into main Jul 5, 2026
4 checks passed
@SaumilP
SaumilP deleted the feature/overall-enhancements branch July 5, 2026 12:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant