Reusable Spring Boot building blocks for backend applications: authentication (JWT + API key),
local user mirror, tags, key/value settings, outbound webhooks, and row-level multi-tenancy. The
library bundles each feature as an independent Spring @Configuration, so applications can opt
into the full stack with a single import or pick the parts they need.
- Java 21
- Spring Boot 3.3.x
- A relational database supported by Spring Data JPA (PostgreSQL is used in CI / integration tests)
Releases are published to Maven Central; SNAPSHOTs are published to GitHub Packages on every push
to main.
<dependency>
<groupId>com.open-elements</groupId>
<artifactId>spring-services</artifactId>
<version><!-- latest released version --></version>
</dependency>The simplest way to enable every feature is to import FullSpringServiceConfig:
@SpringBootApplication
@Import(FullSpringServiceConfig.class)
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}Applications that only need a subset can import the individual feature configurations directly —
for example SecurityConfig, TenantConfig, TagConfig, WebhookConfig, SettingsConfig,
ApiKeyConfig. Multi-tenancy can also be enabled declaratively via the @EnableTenant
meta-annotation.
- JWT / OAuth2 Resource Server — Standard Spring Security JWT validation with
roles-claim toROLE_*mapping in addition to the defaultSCOPE_*mapping fromscope/scp. - API Key Authentication —
X-API-Keyheader authentication via a custom Spring Security filter that runs ahead of the bearer-token filter. Keys (crm_prefix + 48 random alphanumeric characters fromSecureRandom) are stored as a SHA-256 hash plus a short display prefix; the raw key is returned exactly once at creation. API keys grant read-only access (GET/HEAD/OPTIONS) — mutating operations require a JWT. - User Service — Lazily provisions a local user row from the JWT subject (
sub) and keepsname,emailandavatarUrlin sync with the matching JWT claims. The avatar URL points directly at the identity provider; clients render it without any proxying by this library. - Tags — CRUD service for color-coded labels. A
PreTagDeleteEventis published before deletion so other features can detach references — or veto the deletion by throwing. - Settings — Plain string-key / string-value store via
SettingsDataService, useful for configuration that lives in the database rather than inapplication.yml. - Webhooks — Outbound webhook subscriptions. Data lifecycle events trigger a webhook dispatch
after the originating transaction commits; HTTP POST is performed asynchronously via Spring
RestClient(10 s connect/read timeout) and the last response status is persisted on the webhook row. Apingoperation is exposed for testing a subscription on demand. - Multi-Tenancy — Row-level isolation in a shared schema. Tenant id is taken from the authenticated principal; a JPA pre-persist guard fails fast if a row would be written without one. Tenant-aware base classes mirror the non-tenant data abstractions and make cross-tenant reads/writes impossible by construction.
- Generic Data Layer — Reusable abstractions (
AbstractEntitywith UUID primary key and audit timestamps,AbstractDbBackedDataServicetemplate) that wire CRUD, transactions and lifecycle events for any domain object. - Lifecycle Events —
OnObjectCreate,OnObjectUpdateandOnObjectDeleteare published synchronously inside the originating transaction; consumers opt into post-commit behaviour with@TransactionalEventListener.
For per-package overviews, see the package-info.java files under
src/main/java/com/openelements/spring/base/.
./mvnw clean verify # build + run tests
./mvnw spotless:apply # apply Google Java Format
./mvnw spotless:check # verify formatting (also runs in CI)src/main/java/com/openelements/spring/base/
├── FullSpringServiceConfig.java — Aggregate @Import of every feature config
├── data/ — Generic CRUD abstractions
├── events/ — Lifecycle events
├── security/ — Filter chain, JWT converter, AuthService
│ ├── apikey/ — X-API-Key authentication filter
│ └── user/ — Local user mirror
├── services/
│ ├── apikey/ — API key data layer
│ ├── settings/ — Key/value settings
│ ├── tag/ — Tag CRUD + PreTagDeleteEvent
│ └── webhook/ — Outbound webhook delivery
└── tenant/ — Multi-tenancy primitives
Every push to main triggers the snapshot.yml GitHub Actions workflow, which builds the project, runs all tests, and
publishes a SNAPSHOT artifact to GitHub Packages at:
https://maven.pkg.github.com/OpenElementsLabs/spring-services
No manual steps are required. SNAPSHOT publishing only happens on main — feature branches only run the build workflow.
Releases are published to Maven Central via JReleaser. The process is triggered locally using release.sh.
-
Create
~/.jreleaser/config.tomlwith the following content and fill in your credentials:# JReleaser configuration for local releases. # # IMPORTANT: The public and secret key values must be valid ASCII-armored PGP keys. # There must be a blank line between the "-----BEGIN ..." header and the key data. JRELEASER_GITHUB_TOKEN = "ghp_your_github_personal_access_token" JRELEASER_MAVENCENTRAL_USERNAME = "your_maven_central_username" JRELEASER_MAVENCENTRAL_TOKEN = "your_maven_central_token" JRELEASER_GPG_PASSPHRASE = "your_gpg_passphrase" JRELEASER_GPG_PUBLIC_KEY = """-----BEGIN PGP PUBLIC KEY BLOCK----- <your base64-encoded public key data> =XXXX -----END PGP PUBLIC KEY BLOCK-----""" JRELEASER_GPG_SECRET_KEY = """-----BEGIN PGP PRIVATE KEY BLOCK----- <your base64-encoded secret key data> =XXXX -----END PGP PRIVATE KEY BLOCK-----"""
| Variable | Description | |---|---| |
JRELEASER_GITHUB_TOKEN| GitHub personal access token (Fine-grained PAT with Contents: Read and Write permission on this repository) | |JRELEASER_MAVENCENTRAL_USERNAME| Maven Central portal username | |JRELEASER_MAVENCENTRAL_TOKEN| Maven Central portal token | |JRELEASER_GPG_PASSPHRASE| GPG key passphrase | |JRELEASER_GPG_PUBLIC_KEY| GPG public key (ASCII-armored) | |JRELEASER_GPG_SECRET_KEY| GPG private key (ASCII-armored) | -
Ensure your GPG key is available and the passphrase matches.
./release.sh <release-version> <next-snapshot-version>Example:
./release.sh 1.0.0 1.1.0-SNAPSHOTThis will:
- Set the project version to
1.0.0 - Build and run all tests (
mvn clean verify) - Commit and push the release version
- Stage artifacts locally (JAR, sources, Javadoc, SBOM)
- Run JReleaser to sign artifacts with GPG, upload to Maven Central, and create a GitHub Release with a changelog
- Set the project version to
1.1.0-SNAPSHOT - Commit and push the next snapshot version
If any step fails, the script stops immediately (set -e). If the failure occurs after the release version commit has
been pushed, manual recovery may be required.
Apache License 2.0 — see LICENSE.