This shared library serves as the foundational backbone in a microservices ecosystem. It encapsulates reusable domain models, utility classes, validation groups, and robust error-handling mechanisms to ensure consistency, eliminate code duplication, and enforce best practices across both Servlet and Reactive microservices.
spring-boot-autoconfigurespring-boot-configuration-processorspring-boot-starter-webspring-boot-starter-webfluxspring-boot-starter-data-jpaZero-Touch Activation & Configuration
@AutoConfiguration files targeting specific runtime profiles.Java 17 records for modern, immutable property mapping.Multi-Tenancy & Context Management
CurrentTenantIdentifierResolver and HibernatePropertiesCustomizer) to enforce tenant boundary checks. Defaults to a fallback tenant (0L) during system bootstrap before switching to strict security verification.ThreadLocal backed context holder storing execution-scoped user and tenant metadata (UserInfo) across service calls.ContextRefreshedEvent to ensure startup tasks operate safely before enforcing tenant access checks.Stack Support & Exception Handling
Reusable Domains & Utils
Advanced Validation
Before consuming or deploying this library, one must authenticate Maven or Gradle with GitHub Packages using a Personal Access Token (PAT).
Generating Token
For Maven project:
Copy the token into global ~/.m2/settings.xml file:
<settings>
<servers>
<server>
<id>github</id>
<username>GITHUB_USERNAME</username>
<password>ghp_ACCESS_TOKEN</password>
</server>
</servers>
</settings>
For Gradle project:
Copy the token into global ~/.gradle/gradle.properties file:
gpr.user=GITHUB_USERNAME
gpr.key=ghp_ACCESS_TOKEN
To publish this library to private package registry,
For Maven, add maven-deploy-plugin plugin and configure distributionManagement inside pom.xml
<distributionManagement>
<repository>
<id>github</id>
<name>GitHub Packages</name>
<url>https://maven.pkg.github.com/Kranthi0307/shared-service</url>
</repository>
</distributionManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.1.1</version>
</plugin>
</plugins>
</build>
For Gradle, add maven-publish plugin and configure the publishing block inside build.gradle
plugins {
id 'maven-publish'
id 'java'
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/Kranthi0307/shared-service")
credentials {
username = project.findProperty("gpr.user")
password = project.findProperty("gpr.key")
}
}
}
}
Run the following command to deploy securely via CI/CD pipeline:
For Maven,
mvn clean deploy
For Gradle,
gradle clean publish
Update the version before deploying.
Integrating the library is seamless. Simply add the following dependency to the hosting application:
Authentication and the source of library needs to be configured
For Maven
<repositories>
<repository>
<id>github</id>
<url>https://maven.pkg.github.com/Kranthi0307/shared-service</url>
</repository>
</repositories>
<dependency>
<groupId>com.kranthi</groupId>
<artifactId>shared-service</artifactId>
<version>1.x.x</version>
</dependency>
For Gradle
repositories {
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/Kranthi0307/shared-service")
credentials {
username = project.findProperty("gpr.user")
password = project.findProperty("gpr.key")
}
}
}
implementation 'com.kranthi:shared-service:1.x.x'
No additional annotations (like
@Enable*) or explicit configuration scans are required. The library auto-configures itself upon building the application.
To provide compilation-time IDE support (like autocomplete for configuration properties) in hosting applications, ensure the spring-boot-configuration-processor is included in the build.
Intelligent Defaults: The library utilizes @ConfigurationProperties bundled with @DefaultValue to provide plug-and-play defaults (e.g., config.web.public-end-points="/actuator/health") without polluting the host application's properties file.
@ConfigurationProperties(prefix = "config.web")
public record MyWebProperties(
@DefaultValue("/actuator/health")
String[] publicEndPoints
) {
}
├───config/ #Framework AutoConfigurations
├───domains/ #Reusable domains, DTOs, and common models
├───exceptions/ #Abstract ecosystem RuntimeExceptions
├───handlers/ #WebMVC/WebFlux @RestControllerAdvice instances
├───properties/ #Java 17 configuration records
├───utils/ #Common thread-safe helper utilities
└───validation/ #Validation groups (CreateValidation, UpdateValidation)
Validation Groups
public interface CreateValidation {}
public interface UpdateValidation {}
Domain
public record ResponseRecord<T>(String message,
T data) {
}
Exception
public abstract class BaseException extends RuntimeException {
private final String errorCode;
protected BaseException(String message, String errorCode) {
super(message);
this.errorCode = errorCode;
}
protected BaseException(String message, String errorCode, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
}
Handler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BaseException.class)
public ResponseEntity<ResponseRecord<String>> handleBaseException(BaseException exception) {
HttpStatus status;
//My exceptions Logic
return ResponseEntity.status(status)
.body(new ResponseRecord<>(
exception.getMessage(),
exception.getErrorCode()
));
}
}
The library provides a structured, centralized error-handling architecture that standardizes API error responses ecosystem-wide without capturing unintended framework or generic runtime exceptions.
BaseException class that extends RuntimeException. All custom business exceptions across the microservices should inherit from this class.Exception.class instances, the global exception handlers explicitly targets the base domain exception:@ExceptionHandler(BaseException.class)
@RestControllerAdvice implementations tailored for SERVLET and REACTIVE applications, ensuring appropriate response serialization regardless of the underlying web stack.Crucial: Bean Naming Collisions
Since this library provisions beans automatically via@AutoConfiguration, one must prevent duplicate bean names between different stack variants (Servlet vs. Reactive).
If a hosting application attempts to build its own bean with a conflicting name (e.g., a genericglobalExceptionHandler), Spring Boot's context initialization will fail.
Rule: Always namespace, auto-configured internal beans clearly (e.g.,servletGlobalExceptionHandlervsreactiveGlobalExceptionHandler) within conditional configuration classes.
To make changes to this library and test them locally in a hosting application:
mvn clean install
ADR 1: Decoupled Architecture via Optional Dependencies
<optional>true</optional> for non-mandatory core dependencies. Hosting applications retain full control over their dependency versions without pollution.ADR 2: Avoided @PropertySource for Default Configurations
@PropertySource is a known anti-pattern in modern Spring Boot ecosystems.@PropertySource entirely. It breaks smooth integration with Java 17 configuration record types and messes with Spring Boot's natural property loading order. Instead, we lean on @ConfigurationProperties with @DefaultValue.ADR 3: Auto-Configuration Split (Servlet vs. Reactive)
Context: The library must safely support both WebMVC (Servlet) and WebFlux (Reactive) microservices without causing runtime bean definition conflicts or forcing developers to manually configure their stack type.
Decision: I made it easy for both Servlet and Reactive environments to work smoothly out of the box by leveraging Spring Boot's modern auto-configuration loading mechanism. The auto-configurations are registered via: resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. Inside the configuration classes, beans are dynamically and cleanly isolated using conditional boundaries based on the host application's runtime environment:
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)This ensures that hosting applications automatically get the correct bean implementations (such as the appropriate GlobalExceptionHandler) with zero manual setup.
ADR 4: Thread-Safe State Management in Multi-Tenant
Context: Tenant identification depends on the application's lifecycle state (isAppStarted). Because Hibernate lifecycle hooks (CurrentTenantIdentifierResolver) invoke tenant resolution requests early during context creation, early web requests or background threads could read stale initialization states. Furthermore, exposing this logic via a shared library introduces risks of cross-context test pollution if shared global memory states are utilized.
Decision: Eliminate global static variables in favor of a Spring-managed bean instance. Mark the state variable as volatile to enforce cross-thread visibility, register it via Spring Boot auto-configuration mechanism, and utilize lazy dependency injection (ObjectProvider<T>) at the resolution layer.
Consequence:
JVM can run reliably without shared-state leaks or cross-contamination.ADR 5: Decoupled Multi-Tenancy Data Dependencies
CurrentTenantIdentifierResolver component hook for database isolation routing. However, this library is imported by multiple downstream microservices, some of which may be purely reactive (e.g., WebFlux API Gateway) or non-relational services that do not require full relational database management (RDBMS) drivers or Hibernate engines.spring-boot-starter-data-jpa dependency utilizing both the <scope>provided</scope> and <optional>true</optional> configuration scopes.<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<optional>true</optional>
<scope>provided</scope>
</dependency>
JPA/Hibernate libraries forced into their dependency graphs unless they explicitly declare a JPA starter themselves.Spring Cloud Gateway applications.CurrentTenantIdentifierResolver) during the shared library's internal build lifecycle so it compiles seamlessly.spring-boot-configuration-processor plugin active and IDE's annotation processing is enabled..m2 cache is likely polluted. Do not delete the entire .m2 folder. Instead, delete only this specific library's cache by navigating to ~/.m2/repository/ and deleting the specific library or version folder. After deleting, re-import the dependencies in the hosting application to pull the correct version.The following capabilities are planned for upcoming releases: