Skip to content

Java: Add OpenFeature provider#59

Merged
msiebert merged 17 commits intomasterfrom
msiebert-openfeature-provider
Apr 9, 2026
Merged

Java: Add OpenFeature provider#59
msiebert merged 17 commits intomasterfrom
msiebert-openfeature-provider

Conversation

@msiebert
Copy link
Copy Markdown
Contributor

Summary

  • Adds an OpenFeature provider (MixpanelProvider) as a separate Maven subproject (openfeature-provider/) that wraps the Mixpanel Java SDK's BaseFlagsProvider
  • Implements all five OpenFeature evaluation types: boolean, string, integer, double, and object
  • Uses OpenFeature Java SDK dev.openfeature:sdk:1.20.1

Design Decisions

  • Wraps BaseFlagsProvider: Accepts any flags provider (local or remote) via the constructor
  • Global context via initialize(): Context is set once through the OpenFeature initialize(EvaluationContext) lifecycle method and stored in a field. Per-evaluation context parameters are ignored.
  • PROVIDER_NOT_READY for local providers: When wrapping a LocalFlagsProvider, checks areFlagsReady() before each evaluation. Skipped for remote providers (always ready).
  • STATIC reason: All successful resolutions use reason STATIC; all failures use ERROR
  • targetingKey not special: Treated as a regular context property — no mapping to distinct_id
  • Error codes: FLAG_NOT_FOUND (fallback returned), TYPE_MISMATCH (wrong type), PROVIDER_NOT_READY (local flags not loaded), GENERAL (exceptions)
  • Numeric coercion: Integer evaluation accepts Long/Double with overflow protection; Double evaluation accepts Integer/Long
  • shutdown() is a no-op

Test Coverage

29 tests covering:

  • All five flag types (boolean, string, integer, double, object) — success and type mismatch
  • Flag-not-found for all types
  • PROVIDER_NOT_READY with LocalFlagsProvider (ready and not ready)
  • PROVIDER_NOT_READY skipped for non-local providers
  • Global context set via initialize, per-eval context ignored
  • targetingKey passed as regular property
  • Integer overflow returns TYPE_MISMATCH
  • Exception handling returns default values
  • Metadata name and shutdown no-op

Test plan

  • mvn test in openfeature-provider/ — 29/29 passing

🤖 Generated with Claude Code

Implements an OpenFeature FeatureProvider that wraps BaseFlagsProvider,
enabling Mixpanel feature flags to be used via the OpenFeature standard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@msiebert msiebert self-assigned this Mar 19, 2026
msiebert and others added 2 commits March 20, 2026 11:05
The OpenFeature SDK merges all context layers before calling the
provider. The provider was ignoring this merged context and using a
stored global context from initialize(). Now uses convertContext(ctx)
in evaluate() and getObjectEvaluation(). Also adds variant key
passthrough and null variant key tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- shutdown() now delegates to underlying flags provider via shutdown()
- Add shutdown() to BaseFlagsProvider (no-op) and LocalFlagsProvider (calls close())
- Reject non-whole decimals in integer coercion (42.5 → TYPE_MISMATCH)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@msiebert msiebert changed the title Add OpenFeature provider Java: Add OpenFeature provider Mar 31, 2026
msiebert and others added 8 commits April 1, 2026 12:49
Extract fetchVariant, errorResult, and successResult helpers to
eliminate 6 duplicated builder patterns. Add proper imports for
Arrays and ArrayList. Extract buildResult in LocalFlagsProvider
to consolidate variant construction and exposure tracking. Simplify
isQaTester tracking and collapse nested runtime condition checks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ants

Extract targetingKey from EvaluationContext.getTargetingKey() explicitly
in convertContext. Add Object[] array handling in objectToValue. Replace
string literal reasons with Reason.STATIC and Reason.ERROR constants.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename artifact from mixpanel-java-openfeature-provider to
mixpanel-java-openfeature, give it its own version (0.1.0) independent
of the main SDK, and add Maven Central publishing plugins (source,
javadoc, GPG signing, central-publishing).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Org policy requires actions pinned to full-length commit SHAs instead of
version tags. Also skip GPG signing when installing the core library
locally in the OpenFeature provider job.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OpenFeature SDK 1.20.1 requires Java 11+ (class version 55.0), so the
provider cannot compile under Java 8.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes CodeQL actions/missing-workflow-permissions alert by declaring
explicit read-only contents permission.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Deduplicate getObjectEvaluation and evaluate by extracting shared logic
into a common evaluate method that accepts a mapper function. Also log
exceptions during shutdown instead of silently ignoring them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@msiebert msiebert marked this pull request as ready for review April 3, 2026 05:17
@msiebert msiebert requested review from a team and rahul-mixpanel April 3, 2026 05:17
Overloaded constructors accept a token and LocalFlagsConfig or
RemoteFlagsConfig, create the MixpanelAPI internally, auto-start
polling for local configs, and expose the client via getMixpanel().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
msiebert and others added 3 commits April 7, 2026 14:18
… flag not found

Updates OpenFeature provider reason codes to match the updated spec:
- Success: STATIC → TARGETING_MATCH
- Flag not found: ERROR → DEFAULT

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a manual-dispatch GitHub Actions workflow to publish the
OpenFeature provider to Maven Central independently from the
core SDK release cycle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment on lines +13 to +87
runs-on: ubuntu-latest
outputs:
version: ${{ steps.set-version.outputs.version }}

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 8
uses: actions/setup-java@v4
with:
java-version: '8'
distribution: 'temurin'

- name: Cache Maven dependencies
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-

- name: Import GPG key
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
echo "$GPG_PRIVATE_KEY" | base64 --decode | gpg --batch --import
echo "allow-preset-passphrase" >> ~/.gnupg/gpg-agent.conf
echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf
gpg --list-secret-keys --keyid-format LONG

- name: Configure Maven settings
env:
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }}
run: |
mkdir -p ~/.m2
cat > ~/.m2/settings.xml << EOF
<settings>
<servers>
<server>
<id>central</id>
<username>${MAVEN_CENTRAL_USERNAME}</username>
<password>${MAVEN_CENTRAL_TOKEN}</password>
</server>
</servers>
</settings>
EOF

- name: Set version
id: set-version
run: |
VERSION=${{ github.event.inputs.version }}
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "version=$VERSION" >> $GITHUB_OUTPUT
cd openfeature-provider
mvn versions:set -DnewVersion=$VERSION -DgenerateBackupPoms=false

- name: Build and install Main SDK locally
run: mvn clean install -DskipTests -Dgpg.skip=true

- name: Run tests - OpenFeature Provider
run: |
cd openfeature-provider
mvn clean test

- name: Deploy OpenFeature Provider to Maven Central
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
cd openfeature-provider
mvn clean deploy -Dgpg.passphrase=$GPG_PASSPHRASE

verify:
Comment on lines +88 to +107
needs: release
runs-on: ubuntu-latest
if: success()

steps:
- name: Wait for Maven Central sync
run: sleep 300 # Wait 5 minutes for synchronization

- name: Verify artifact on Maven Central
run: |
VERSION=${{ needs.release.outputs.version }}

RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" https://repo1.maven.org/maven2/com/mixpanel/mixpanel-java-openfeature/${VERSION}/mixpanel-java-openfeature-${VERSION}.jar)
if [ $RESPONSE -eq 200 ]; then
echo "✅ OpenFeature Provider successfully published to Maven Central"
else
echo "⚠️ OpenFeature Provider not yet available on Maven Central (HTTP $RESPONSE)"
fi

echo "Note: Artifacts may take up to 30 minutes to appear on Maven Central"
* Shuts down this provider and releases any resources.
* Subclasses should override this to perform cleanup.
*/
public void shutdown() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing but the MixpanelProvider, BaseFlagsProvider, and LocalFlagsProvider should be using this, correct? This would technically be a breaking change, but I don't think the intent is there for other providers than the ones we provide?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right!

…ency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@msiebert msiebert merged commit 26a488c into master Apr 9, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants