Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
52 changes: 51 additions & 1 deletion CLAUDE.md → .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ mvn -Dtest=ClassName test
mvn -Dtest=ClassName#methodName test
```

Tests are written in **Spock** (Groovy) in most modules.
Tests are written in **Spock** (Groovy) in most modules. Spock is preferred for ALL tests and should be used when attempting to write tests. It relies on the composite-testing dependency which needs to be installed as a scope: `test`
for each project. It is located at `support/composite-logging/pom.xml`.

## Architecture

Expand Down Expand Up @@ -82,6 +83,55 @@ ClientContext ctx = fhConfig.newContext()
boolean enabled = ctx.isEnabled("MY_FEATURE");
```

### Jackson Abstraction Pattern

This repository deliberately abstracts all Jackson JSON functionality behind an interface so that
modules remain independent of the Jackson major version in use at runtime.

**Three libraries form the pattern:**

- **`support/common-jackson`** (`io.featurehub.sdk.common:common-jackson`) — the API-only module.
Contains the `JavascriptObjectMapper` interface and nothing else. Has no dependency on any Jackson
library itself. This is the only Jackson-related artifact that production code should depend on.

- **`support/common-jacksonv2`** (`io.featurehub.sdk.common:common-jacksonv2`) — implements
`JavascriptObjectMapper` using Jackson 2.x (`com.fasterxml.jackson`). Registered via Java
`ServiceLoader` so it is discovered automatically when on the classpath.

- **`v17-and-above/support/common-jacksonv3`** (`io.featurehub.sdk.common:common-jacksonv3`) —
implements `JavascriptObjectMapper` using Jackson 3.x (`tools.jackson`). Java 17+ only.
Also registered via `ServiceLoader`.

**Rules when writing or modifying code:**

1. **Never add `jackson-databind`, `jackson-core`, or any `com.fasterxml.jackson` / `tools.jackson`
dependency directly to a production module's `pom.xml`.** If JSON functionality is needed,
depend on `common-jackson` instead and use the `JavascriptObjectMapper` interface.

2. **If the required functionality is not available on the `JavascriptObjectMapper` interface,
stop and ask for direction** — do not reach for Jackson directly or widen the interface
without discussion.

3. **For tests that need real Jackson behaviour** (e.g. to back a mock or verify serialisation),
add `common-jacksonv2` as a `<scope>test</scope>` dependency. This provides a concrete
implementation without polluting production code with a Jackson version choice.

**Example test pom.xml entry:**

```xml
<dependency>
<groupId>io.featurehub.sdk.common</groupId>
<artifactId>common-jacksonv2</artifactId>
<version>[1.1, 2)</version>
<scope>test</scope>
</dependency>
```
### Notes when writing code

- **Source code** - NEVER try and extract meaning from .class or jar files, always ask the user for the location of the source. The user can always provide it to make understanding how to use the library more simple, often including documentation. Redis and SnakeYAML are examples of this.
- If code is not compiling when running the `mvn` command and it depends on an API from another module in this same repository, it may be that it has changed in source, but that source has not been installed into the local maven repository ($HOME/.m2/repository). Always try to do a `mvn install` in the folder of that specific module that is the root of the problem. Often this is `core/client-java-core` as it is the central module for most code.


### Build Infrastructure Notes

- **Maven Tiles** (`support/tile-java8`, `tile-java11`, `tile-java21`, `tile-sdk`, `tile-release`) provide shared plugin/compiler configuration. The `pom-tiles.xml` in `support/` must be installed before any other module.
Expand Down
15 changes: 15 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

# Verify that the module change files are up to date before pushing.
# If they are stale, run ./detect_module_changes.sh to regenerate them,
# then stage and commit the result before pushing again.

./detect_module_changes.sh --diff
STATUS=$?

if [[ $STATUS -ne 0 ]]; then
echo ""
echo "Push blocked: module change files are out of date."
echo "Run './detect_module_changes.sh' to regenerate them, then commit and push again."
exit 1
fi
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ updates:
directory: "/" # Location of package manifests
open-pull-requests-limit: 0 # only open security PRs
schedule:
interval: "daily"
interval: "weekly"
20 changes: 17 additions & 3 deletions .github/workflows/java.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@ name: Java CI

on: [push]

# for release use https://github.com/gh-a-sample/github-actions-maven-release-sample

jobs:
diff-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: check diff is correct
run: git fetch origin main && git checkout main && git reset --hard origin/main && git checkout ${{ github.head_ref || github.ref }} && bash detect_module_changes.sh --diff
build-java11:
runs-on: ubuntu-latest
needs: diff-check
steps:
- uses: actions/checkout@v5
- name: Set up JDK 11
Expand All @@ -16,14 +27,17 @@ jobs:
- name: Install tiles
run: cd support && mvn -f pom-tiles.xml install
- name: Install support composites
run: mvn install
run: mvn install -pl $(cat java11_changed.txt)
build-java21:
runs-on: ubuntu-latest
needs: diff-check
strategy:
matrix:
java-version: ['17', '21', '25']
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 40
- name: Set up Java ${{ matrix.java-version }}
uses: actions/setup-java@v5
with:
Expand All @@ -33,7 +47,7 @@ jobs:
- name: Install tiles
run: cd support && mvn -f pom-tiles.xml install
- name: All other things
run: mvn install
run: mvn install -pl $(cat java11_changed.txt)
- name: java17+ only
working-directory: v17-and-above
run: mvn install
run: mvn install -pl $(cat java17_changed.txt)
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,4 @@ node-js
front-end-changed.projects

/examples/todo-cuke-java/
/.claude/settings.local.json
22 changes: 22 additions & 0 deletions core/client-java-core/CHANGELOG.adoc → CHANGELOG.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
=== Changes

=== 5.0 (core), 4.x (clients)
- addition of the environmentId and strategyId (if any) for features from the usage API
- simplificiation of the polling active/rest so it is more testable, common functionality removed from RestApi clients in each supported platform to make them easier to implement. This is a breaking change in the client/core contract so we have bumped the version.
- addition of external sources of features using `RawUpdateFeatureListener` and sources
- addition of a standard Redis and Yaml backing stores
- support for empty repository for local development (no edge or api keys)
- separation of Usage classes into interface and implementation. Although this is a breaking change, I have decided not to 5.0 it as it is so recent.
- addition of a generic Conversion utility to allow to guess the type coming
from an external system for yaml and 3rd party external support
- a new `ExtendedFeatureValueInterceptor` that passes the whole feature you are trying to intercept plus the repository to aid in operation. Existing `FeatureValueInterceptors` still work but are deprecated
- addition of change detection for supporting pipeline releases, so we
can have Gitlab do releases.
- addition of Claude Code support

=== 4.2
- Minor patch update to fix a close-out issue with the polling API

=== 4.1
- The separation of client libraries into support for Jersey 2, Jersey 3 and OKHTTP - with all SDK api clients (SSE, REST and the TestApi) included.
- The addition of the Usage API to give pluggable analytics, with the addition
of support for OpenTelemetry tracking and Segment Tracking.

=== 3.3
- Support array values in client side evaluation. This was rolled out to the other SDKs but not Java. The SDK can now be given an array of attributes and compare them against an array of values in a Strategy.

Expand Down
119 changes: 119 additions & 0 deletions CONTRIBUTING.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
= Developer Guide
:toc:
:toc-placement: preamble

== Build

The build must be done in two phases: first the `support/` directory (Maven Tiles + composite POMs), then the root reactor.

[source,bash]
----
# Build without tests (fast)
./build_only.sh

# Build with tests
./build_all_and_test.sh
----

Java 17+ modules are built separately and require JDK 17 or above:

[source,bash]
----
cd v17-and-above && mvn install
----

=== Running Tests

[source,bash]
----
# All tests in a module
cd core/client-java-core && mvn test

# Single test class
mvn -Dtest=ClassName test

# Single test method
mvn -Dtest=ClassName#methodName test
----

== Module Change Tracking

CI uses three generated files to know which modules need to be built and released.
These files are committed to the repository and must be kept up to date whenever
you change modules on a branch.

|===
| File | Contents

| `v17-and-above/java17_changed.txt`
| Modules under `v17-and-above/` that differ from `main`, with the `v17-and-above/` prefix stripped

| `java11_changed.txt`
| All changed modules excluding those under `v17-and-above/`

| `release_modules.txt`
| Cumulative list of Java 11 changed modules (excluding examples and `v17-and-above/`) accumulated
across merges to `main`, so multiple branches can be released together in a single batch.

| `v17-and-above/release_modules.txt`
| Same as above but for Java 17 modules only, with the `v17-and-above/` prefix stripped.
|===

To regenerate all three files:

[source,bash]
----
./detect_module_changes.sh
----

To check whether the files are up to date without writing anything:

[source,bash]
----
./detect_module_changes.sh --diff
----

This exits with code `1` and prints a diff if any file is stale.

=== Git Hook

A pre-push hook is provided that automatically runs the `--diff` check before
every push, blocking the push if the files are out of date.

==== Installing the Hook

Option A — configure Git to use the checked-in hooks directory (recommended, applies to all hooks):

[source,bash]
----
git config core.hooksPath .githooks
----

Option B — copy the hook manually:

[source,bash]
----
cp .githooks/pre-push .git/hooks/pre-push && chmod +x .git/hooks/pre-push
----

==== When the Hook Blocks Your Push

If you see:

----
Push blocked: module change files are out of date.
Run './detect_module_changes.sh' to regenerate them, then commit and push again.
----

Run the following to fix it:

[source,bash]
----
./detect_module_changes.sh # regenerates and stages the files
git commit -m "chore: update module change files"
git push
----

NOTE: `release_modules.txt` is cumulative — running the script merges the current branch's
modules into any that were already listed in the file from prior merges.
To start a fresh release cycle, clear the file and commit it before merging further branches.
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM eclipse-temurin:25-jdk-alpine

ARG client
ARG exampleFolder

WORKDIR /app
COPY . /app/
RUN cd support && mvn -DskipTests -f pom-tiles.xml install && mvn -DskipTests install
RUN cd core && mvn -DskipTests install && cd ../client-implementations/$client && mvn -DskipTests install
RUN cd $exampleFolder && mvn -DskipTests package
Loading