diff --git a/.github/component_owners.yml b/.github/component_owners.yml
index 4372c0a2a..b2aef428d 100644
--- a/.github/component_owners.yml
+++ b/.github/component_owners.yml
@@ -19,6 +19,9 @@ components:
- thomaspoignant
providers/jsonlogic-eval-provider:
- justinabrahms
+ providers/togglz:
+ - liran2000
+ - bennetelli
ignored-authors:
- renovate-bot
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 50d38c56f..ebbec1753 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,7 @@
providers/go-feature-flag
providers/jsonlogic-eval-provider
providers/env-var
+ providers/togglz
diff --git a/providers/togglz/CHANGELOG.md b/providers/togglz/CHANGELOG.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/providers/togglz/README.md b/providers/togglz/README.md
new file mode 100644
index 000000000..e7735f63e
--- /dev/null
+++ b/providers/togglz/README.md
@@ -0,0 +1,42 @@
+# Unofficial Togglz OpenFeature Provider for Java
+
+Togglz OpenFeature Provider can provide usage for Togglz via OpenFeature Java SDK.
+
+## Installation
+
+
+
+```xml
+
+
+ dev.openfeature.contrib.providers
+ togglz
+ 0.0.1
+
+```
+
+
+
+## Usage
+Togglz OpenFeature Provider is using Togglz Java SDK.
+
+### Usage Example
+
+```
+TogglzOptions togglzOptions = TogglzOptions.builder().features(features).build();
+FeatureProvider featureProvider = new TogglzProvider(togglzOptions);
+api.setProviderAndWait(featureProvider);
+client = api.getClient();
+boolean featureEnabled = client.getBooleanValue(TestFeatures.FEATURE_ONE.name(), false);
+
+```
+
+See [TogglzProviderTest.java](./src/test/java/dev/openfeature/contrib/providers/togglz/TogglzProviderTest.java) for more information.
+
+## Caveats / Limitations
+
+* Togglz OpenFeature Provider only supports boolean feature flags.
+* Evaluation does not treat evaluation context, but relies on Togglz functionalities.
+
+## References
+* [Togglz](https://www.togglz.org)
diff --git a/providers/togglz/lombok.config b/providers/togglz/lombok.config
new file mode 100644
index 000000000..bcd1afdae
--- /dev/null
+++ b/providers/togglz/lombok.config
@@ -0,0 +1,5 @@
+# This file is needed to avoid errors throw by findbugs when working with lombok.
+lombok.addSuppressWarnings = true
+lombok.addLombokGeneratedAnnotation = true
+config.stopBubbling = true
+lombok.extern.findbugs.addSuppressFBWarnings = true
diff --git a/providers/togglz/pom.xml b/providers/togglz/pom.xml
new file mode 100644
index 000000000..804eefdc0
--- /dev/null
+++ b/providers/togglz/pom.xml
@@ -0,0 +1,40 @@
+
+
+ 4.0.0
+
+ dev.openfeature.contrib
+ parent
+ 0.1.0
+ ../../pom.xml
+
+ dev.openfeature.contrib.providers
+ togglz
+ 0.0.1
+
+ togglz
+ togglz provider for Java
+ https://www.togglz.org/
+
+
+
+ org.togglz
+ togglz-core
+ 3.3.0
+
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.7
+
+
+
+ org.apache.logging.log4j
+ log4j-slf4j2-impl
+ 2.20.0
+ test
+
+
+
+
diff --git a/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzOptions.java b/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzOptions.java
new file mode 100644
index 000000000..413262d4c
--- /dev/null
+++ b/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzOptions.java
@@ -0,0 +1,16 @@
+package dev.openfeature.contrib.providers.togglz;
+
+import lombok.Builder;
+import lombok.Data;
+import org.togglz.core.Feature;
+
+import java.util.Collection;
+
+/**
+ * Togglz Options for initializing Togglz provider.
+ */
+@Data
+@Builder
+public class TogglzOptions {
+ Collection features;
+}
diff --git a/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzProvider.java b/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzProvider.java
new file mode 100644
index 000000000..54bb1545a
--- /dev/null
+++ b/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzProvider.java
@@ -0,0 +1,98 @@
+package dev.openfeature.contrib.providers.togglz;
+
+import dev.openfeature.sdk.EvaluationContext;
+import dev.openfeature.sdk.EventProvider;
+import dev.openfeature.sdk.Metadata;
+import dev.openfeature.sdk.ProviderEvaluation;
+import dev.openfeature.sdk.ProviderState;
+import dev.openfeature.sdk.Reason;
+import dev.openfeature.sdk.Value;
+import dev.openfeature.sdk.exceptions.FlagNotFoundError;
+import dev.openfeature.sdk.exceptions.GeneralError;
+import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
+import dev.openfeature.sdk.exceptions.TypeMismatchError;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.togglz.core.Feature;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Provider implementation for Togglz.
+ */
+@AllArgsConstructor
+@Slf4j
+public class TogglzProvider extends EventProvider {
+
+ @Getter
+ private static final String NAME = "Togglz Provider";
+ public static final String NOT_IMPLEMENTED =
+ "Not implemented - provider does not support this type. Only boolean is supported.";
+
+ private Map features = new HashMap<>();
+
+ @Getter
+ private ProviderState state = ProviderState.NOT_READY;
+
+ public TogglzProvider(TogglzOptions togglzOptions) {
+ togglzOptions.features.forEach(feature -> features.put(feature.name(), feature));
+ }
+
+ /**
+ * Initialize the provider.
+ * @param evaluationContext evaluation context
+ * @throws Exception on error
+ */
+ @Override
+ public void initialize(EvaluationContext evaluationContext) throws Exception {
+ super.initialize(evaluationContext);
+ state = ProviderState.READY;
+ log.debug("finished initializing provider, state: {}", state);
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return () -> NAME;
+ }
+
+ @Override
+ public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
+ if (!ProviderState.READY.equals(state)) {
+ if (ProviderState.NOT_READY.equals(state)) {
+ throw new ProviderNotReadyError("provider not yet initialized");
+ }
+ throw new GeneralError("unknown error");
+ }
+ Feature feature = features.get(key);
+ if (feature == null) {
+ throw new FlagNotFoundError("flag " + key + " not found");
+ }
+ boolean featureBooleanValue = feature.isActive();
+ return ProviderEvaluation.builder()
+ .value(featureBooleanValue)
+ .reason(Reason.TARGETING_MATCH.name())
+ .build();
+ }
+
+ @Override
+ public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
+ throw new TypeMismatchError(NOT_IMPLEMENTED);
+ }
+
+ @Override
+ public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
+ throw new TypeMismatchError(NOT_IMPLEMENTED);
+ }
+
+ @Override
+ public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
+ throw new TypeMismatchError(NOT_IMPLEMENTED);
+ }
+
+ @Override
+ public ProviderEvaluation getObjectEvaluation(String s, Value value, EvaluationContext evaluationContext) {
+ throw new TypeMismatchError(NOT_IMPLEMENTED);
+ }
+}
diff --git a/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TestFeatures.java b/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TestFeatures.java
new file mode 100644
index 000000000..a3f862ac0
--- /dev/null
+++ b/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TestFeatures.java
@@ -0,0 +1,18 @@
+package dev.openfeature.contrib.providers.togglz;
+
+import org.togglz.core.Feature;
+import org.togglz.core.annotation.Label;
+import org.togglz.core.context.FeatureContext;
+
+public enum TestFeatures implements Feature {
+
+ @Label("First Feature")
+ FEATURE_ONE,
+
+ @Label("Second Feature")
+ FEATURE_TWO;
+
+ public boolean isActive() {
+ return FeatureContext.getFeatureManager().isActive(this);
+ }
+}
diff --git a/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TogglzProviderTest.java b/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TogglzProviderTest.java
new file mode 100644
index 000000000..2250b2333
--- /dev/null
+++ b/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TogglzProviderTest.java
@@ -0,0 +1,82 @@
+package dev.openfeature.contrib.providers.togglz;
+
+import dev.openfeature.sdk.Client;
+import dev.openfeature.sdk.FeatureProvider;
+import dev.openfeature.sdk.ImmutableContext;
+import dev.openfeature.sdk.OpenFeatureAPI;
+import dev.openfeature.sdk.exceptions.FlagNotFoundError;
+import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
+import dev.openfeature.sdk.exceptions.TypeMismatchError;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.togglz.core.context.StaticFeatureManagerProvider;
+import org.togglz.core.manager.FeatureManager;
+import org.togglz.core.manager.FeatureManagerBuilder;
+import org.togglz.core.repository.FeatureState;
+import org.togglz.core.repository.StateRepository;
+import org.togglz.core.repository.mem.InMemoryStateRepository;
+import org.togglz.core.user.NoOpUserProvider;
+
+import java.util.Arrays;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class TogglzProviderTest {
+
+ private FeatureProvider featureProvider;
+
+ private Client client;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ StateRepository stateRepository = new InMemoryStateRepository();
+ stateRepository.setFeatureState(new FeatureState(TestFeatures.FEATURE_ONE, true));
+ stateRepository.setFeatureState(new FeatureState(TestFeatures.FEATURE_TWO, false));
+
+ FeatureManager featureManager = new FeatureManagerBuilder()
+ .featureEnums(TestFeatures.class)
+ .stateRepository(stateRepository)
+ .userProvider(new NoOpUserProvider())
+ .build();
+ StaticFeatureManagerProvider.setFeatureManager(featureManager);
+
+ TogglzOptions togglzOptions = TogglzOptions.builder().features(Arrays.asList(TestFeatures.values())).build();
+ featureProvider = new TogglzProvider(togglzOptions);
+ OpenFeatureAPI.getInstance().setProviderAndWait(featureProvider);
+ client = OpenFeatureAPI.getInstance().getClient();
+ }
+
+ @Test
+ void getBooleanEvaluation() {
+ assertEquals(true, featureProvider.getBooleanEvaluation(TestFeatures.FEATURE_ONE.name(), false, new ImmutableContext()).getValue());
+ assertEquals(true, client.getBooleanValue(TestFeatures.FEATURE_ONE.name(), false));
+ assertEquals(false, featureProvider.getBooleanEvaluation(TestFeatures.FEATURE_TWO.name(), false, new ImmutableContext()).getValue());
+ assertEquals(false, client.getBooleanValue(TestFeatures.FEATURE_TWO.name(), false));
+ }
+
+ @Test
+ void notFound() {
+ assertThrows(FlagNotFoundError.class, () -> {
+ featureProvider.getBooleanEvaluation("not-found-flag", false, new ImmutableContext());
+ });
+ }
+
+ @Test
+ void typeMismatch() {
+ assertThrows(TypeMismatchError.class, () -> {
+ featureProvider.getStringEvaluation(TestFeatures.FEATURE_ONE.name(), "default_value", new ImmutableContext());
+ });
+ }
+
+ @SneakyThrows
+ @Test
+ void shouldThrowIfNotInitialized() {
+ TogglzOptions togglzOptions = TogglzOptions.builder().features(Arrays.asList(TestFeatures.values())).build();
+ FeatureProvider togglzProvider = new TogglzProvider(togglzOptions);
+
+ // ErrorCode.PROVIDER_NOT_READY should be returned when evaluated via the client
+ assertThrows(ProviderNotReadyError.class, ()-> togglzProvider.getBooleanEvaluation("fail_not_initialized", false, new ImmutableContext()));
+ }
+}
\ No newline at end of file
diff --git a/providers/togglz/src/test/resources/log4j2-test.xml b/providers/togglz/src/test/resources/log4j2-test.xml
new file mode 100644
index 000000000..223d21a89
--- /dev/null
+++ b/providers/togglz/src/test/resources/log4j2-test.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/providers/togglz/version.txt b/providers/togglz/version.txt
new file mode 100644
index 000000000..8acdd82b7
--- /dev/null
+++ b/providers/togglz/version.txt
@@ -0,0 +1 @@
+0.0.1