Java Library Pattern — Reusable JAR via GitHub Packages
1. Overview
A Java library in EMS is a standalone Maven JAR module that publishes reusable code to GitHub Packages and is consumed by one or more services as a Maven dependency. It does not run on its own, has no container image, no Kubernetes deployment, and no observability surface — those concerns belong to the consumer service.
The library set is small and load-bearing:
| Library | Consumed by |
|---|---|
|
|
|
|
|
|
|
|
This page is a specialisation of Microservice Pattern. It re-uses the Maven-module and CI/CD halves of that baseline and explicitly drops everything below the JAR — there is no Jib build, no Helm chart, no ArgoCD manifest, no OTel agent, no health probes, no port assignment, no Spring profile model. Portal Pattern and MCP Pattern are sibling specialisations for runtime services.
2. When to Use This Pattern
Use this pattern for any new Maven artefact intended to:
-
Be consumed as a library dependency (
za.co.idealogic:<artefact>) by one or more EMS services. -
Be published to the
github-christhonieGitHub Packages registry on every push todevelop(-SNAPSHOT) and on every release merge tomain(release version). -
Follow the EMS GitFlow release cadence (
mvn gitflow:release-start→ release PR → merge tomain→ tag + bump).
If the artefact runs on its own (has a main class, listens on a port, has its own deploy lifecycle), it belongs to Microservice Pattern (with Portal or MCP specialisation as appropriate) — not here.
3. Relationship to the Microservice Pattern
| Concern | Microservice | Library |
|---|---|---|
Maven module shape |
Single jar artefact, parented to |
Same shape. Every library parents to |
GitHub Packages publishing |
|
Same target registry (one host repo for the whole fleet keeps consumer |
GitHub Actions wrappers |
Five wrappers (push-dev, push-main, push-release, pr-non-main, manual-release-start) |
Four wrappers — drop |
Reusable workflows used |
|
Subset only: |
GitFlow release management |
|
Same plugin, inherited from parent-pom. |
Container image |
Jib build, OTel agent baked in, Docker Hub push |
Not applicable. A library has no runtime image. |
Helm chart, ArgoCD manifests, ports, probes, profiles |
Per the microservice baseline |
Not applicable. |
Required repo secrets |
|
Only |
4. Maven Module Structure
4.1. POM essentials
Every EMS library parents to za.co.idealogic:event and inherits CI-friendly versioning, gitflow-maven-plugin, flatten-maven-plugin, maven-deploy-plugin, the standard surefire/failsafe configuration, and the <distributionManagement> block. The library POM only adds what’s specific to it — its dependencies and any per-module plugin tweaks.
<parent>
<groupId>za.co.idealogic</groupId>
<artifactId>event</artifactId>
<version>1.3.1</version>
</parent>
<artifactId>my-library</artifactId>
<version>${revision}</version>
<properties>
<revision>0.1.0-SNAPSHOT</revision>
</properties>
<repositories>
<repository>
<id>github-christhonie</id>
<url>https://maven.pkg.github.com/christhonie/event</url>
<releases><enabled>true</enabled><checksumPolicy>fail</checksumPolicy></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
<distributionManagement> is inherited from the parent-pom — do not redeclare it. The <repositories> block is duplicated by convention (matching event-database/pom.xml) so a clean checkout can resolve the parent before Maven has read its repositories list.
4.2. Bringing in Spring-Boot dependency management
event:1.3.1 does not import the spring-boot-dependencies BOM globally — each library opts in if it needs Spring-managed versions for its own deps. Libraries that consume Spring (e.g. event-database, spreadsheet-importer) add the BOM to their own <dependencyManagement>:
<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>
</dependencies>
</dependencyManagement>
${spring-boot.version} resolves from the parent-pom (currently 3.4.5 on event:1.3.x). Libraries with no Spring dependencies (a pure utility JAR) can omit this block entirely.
Reference implementations: database/pom.xml, spreadsheet-import/pom.xml.
gitflow-maven-plugin requires both a develop and a main branch on the remote with the conventional GitFlow layout. New libraries should bootstrap those branches before the first manual-release-start run.
4.3. Why one host repo for all artefacts
Every EMS library publishes to https://maven.pkg.github.com/christhonie/event regardless of which GitHub repo it lives in. This is a deliberate workaround for the way GitHub Packages binds artefacts to a Solution Architect/{repo} pair: a single host repo means consumers add one <repository> entry to their POM (or inherit it from parent-pom) instead of one per source repo. The trade-off is that the package list on christhonie/event is the union of every published artefact across the fleet — that is fine, and it mirrors what the parent-pom already does for wordpress-database and event-database.
5. CI/CD
Libraries use the same reusable-workflow set documented in Microservice Pattern § CI/CD and detailed in GitHub Actions CI/CD — they just consume a smaller subset.
5.1. Wrapper inventory
| Wrapper | Trigger and purpose |
|---|---|
|
Push to |
|
Push to |
|
Push to |
|
|
There is no pr-non-main.yml and no docker-build/helm-build/argocd-update jobs — a library never produces a runtime artefact.
5.2. Required repository settings
Mirror the bring-up steps in GitHub Actions CI/CD § Required Repository Settings except the Docker / ArgoCD bits that don’t apply:
-
Repo visibility = private.
christhonie/eventis private; GitHub blocks a public calling repo from referencing reusable workflows in a private source repo, regardless of the access-level setting. Every EMS Maven repo (event-database,wordpress-database,admin-service, …) is private; new library repos must match. Symptom of a mismatch:push-dev.ymlruns fail at 0s asstartup_failurewith no jobs and no log output, while a non-reusable diagnostic workflow on the same repo runs fine. -
Default workflow permissions:
Read and write(otherwisedependencygraph.ymlandgitflow-release-finish.ymlfail at startup with no log output). -
Reusable workflow access on
christhonie/eventset toAccessible from repositories owned by the user 'christhonie'(configured once on the parent-pom repo, applies fleet-wide). -
Repository secret
EVENT_PACKAGE_REPO_TOKEN— a PAT (or fine-grained token) withread:packages+write:packagesscopes. Used for both consuming SNAPSHOTs fromchristhonie/eventand publishing the library’s own artefacts.
DOCKER_PAT and ARGOCD_REPO_TOKEN are not used by any library workflow — do not configure them on library repos.
5.3. Pipeline shape
push to develop push to main (merged release PR)
│ │
▼ ▼
test-be ─► maven-package maven-package (prod)
│ │
┌──────────┼──────────┐ ┌────────┼──────────┐
▼ ▼ ▼ ▼ ▼ ▼
publish maven-deploy dependency- publish maven- gitflow-release-
graph deploy finish
5.4. Wrapper templates
The four library wrappers are small. Reference implementations live under spreadsheet-import/.github/workflows/ and database/.github/workflows/. Inputs / secrets follow the conventions documented in GitHub Actions CI/CD § Consuming Workflows.
6. Bootstrap Checklist
Step-by-step from "git init" to "first SNAPSHOT in GitHub Packages":
-
GitHub repo created (e.g.
christhonie/<library-name>) as private — must match the visibility ofchristhonie/event. Public-→-private callers fail withstartup_failureand no log output. -
Repository workflow permissions flipped to Read and write (
gh api -X PUT repos/christhonie/<repo>/actions/permissions/workflow -f default_workflow_permissions=write -F can_approve_pull_request_reviews=true). -
Repository secret
EVENT_PACKAGE_REPO_TOKENadded. -
Branches
developandmaincreated (gitflow-maven-plugin requires both); setdevelopas the default branch. -
Maven module scaffolded:
-
POM with the shape from § POM essentials. Confirm
<groupId>za.co.idealogic</groupId>and<distributionManagement>(or inheritance) targetshttps://maven.pkg.github.com/christhonie/event. -
Sources under
src/main/java/za/co/idealogic/<package>/…. -
Smoke test under
src/test/java.
-
-
.github/workflows/populated with the four wrappers (push-dev, push-main, push-release, manual-release-start) — copy fromspreadsheet-importordatabase. -
.github/CODEOWNERSadded (* @christhonie). -
.gitattributesadded — LF normalisation for source, CRLF for.bat/.cmd, binary for.jar/images/spreadsheets. -
.gitignoreincludestarget/and.flattened-pom.xml. -
Local verification:
mvn clean install -DskipTestsproduces<artifactId>-<revision>.jarand a flattened POM under~/.m2/repository/za/co/idealogic/<artifactId>/<revision>/with${revision}resolved to the literal version. -
First push to develop triggers
push-dev.yml. Watch viagh run watch. On success, the SNAPSHOT JAR appears underhttps://github.com/christhonie/event/packages(the parent-pom repo’s package list, not the library’s own repo). -
Smoke-consume from a service: add
<dependency><groupId>za.co.idealogic</groupId><artifactId>…</artifactId><version>…-SNAPSHOT</version></dependency>andmvn -U dependency:resolve.
For the first release follow the GitFlow flow: trigger manual-release-start.yml with the target version → review the auto-opened release PR → merge into main → push-main.yml deploys the release version, gitflow-release-finish tags main and bumps develop’s `<revision> to the next SNAPSHOT.
7. Reference Implementations
| Library | Parent | Notes |
|---|---|---|
|
— |
Shared Maven plugin, repository, and distribution settings. Hosts the reusable GitHub Actions workflows under |
|
|
The reference for an EMS-parented library: full GitFlow, JPA + Liquibase + email templates, dependency-graph submission. Source: |
|
|
Pre-existing WordPress schema model. Stable; rarely touched. |
|
|
Library wrapping POI for CSV/XLSX import. Imports the |
8. Anti-Patterns
-
Creating the library repo as public while
christhonie/eventis private. GitHub blocks the cross-repo reusable-workflow call and the run dies at 0s with no log. Match the visibility ofchristhonie/event(private). -
Parenting a new library to
spring-boot-starter-parent(or any other non-EMS parent). You then have to redeclare${revision}, the flatten plugin, the gitflow plugin, the deploy plugin, and<distributionManagement>— all of which are already configured onza.co.idealogic:event. Use the EMS parent and add thespring-boot-dependenciesBOM independencyManagementif you need Spring-managed versions. -
Publishing to a per-library host repo (e.g.
maven.pkg.github.com/christhonie/spreadsheet-import). Every consumer would have to add another<repository>entry. Always publish tochristhonie/event. -
Inlining a
spring-boot-maven-pluginrepackageexecution on a library POM. It produces an executable fat JAR alongside the library JAR and confuses consumers; library JARs are plain. -
Adding
docker-build/helm-buildjobs to a library workflow. The reusable workflows will run, fail (no chart, no Dockerfile), and clutter the run history. Drop them. -
Skipping the
develop+mainbranch setup before the firstmanual-release-start. The gitflow-maven-plugin will fail with a confusing error about missing branches.
9. Further Reading
-
Microservice Pattern — the runtime baseline this pattern specialises away from.
-
GitHub Actions CI/CD — reusable-workflow inventory, required settings, secret conventions.
-
Maven POM Conventions — naming, version flow, plugin defaults.
10. Change History
| Date | Change |
|---|---|
2026-04-29 |
Initial draft. Captures the library-vs-microservice deltas, the single-host-repo convention for GitHub Packages, the four-wrapper CI/CD inventory, and the bootstrap checklist. Triggered by getting |
2026-04-29 |
Drop the "independent library" variant — |
2026-04-29 |
Document the public-→-private visibility constraint for cross-repo reusable workflows (GitHub blocks the call regardless of access-level). Captured as a required setting, an anti-pattern, and a bootstrap-checklist step. Caught while bringing |