diff --git a/.github/component_owners.yml b/.github/component_owners.yml
index 2b280a851..1f42662bc 100644
--- a/.github/component_owners.yml
+++ b/.github/component_owners.yml
@@ -35,6 +35,8 @@ components:
     - novalisdenahi
   providers/statsig:
     - liran2000
+  providers/multiprovider:
+    - liran2000
 
 ignored-authors:
   - renovate-bot
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index f818fd120..69acedd13 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -9,5 +9,6 @@
   "providers/flipt": "0.1.1",
   "providers/configcat": "0.1.0",
   "providers/statsig": "0.1.0",
+  "providers/multiprovider": "0.0.1",
   "tools/junit-openfeature": "0.1.1"
 }
diff --git a/pom.xml b/pom.xml
index f481c1964..10e28c18e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,6 +38,7 @@
         <module>providers/flipt</module>
         <module>providers/configcat</module>
         <module>providers/statsig</module>
+        <module>providers/multiprovider</module>
     </modules>
 
     <scm>
diff --git a/providers/multiprovider/README.md b/providers/multiprovider/README.md
new file mode 100644
index 000000000..d7af9fc26
--- /dev/null
+++ b/providers/multiprovider/README.md
@@ -0,0 +1,86 @@
+# OpenFeature Multi-Provider for Java
+
+The OpenFeature Multi-Provider wraps multiple underlying providers in a unified interface, allowing the SDK client to transparently interact with all those providers at once.
+This allows use cases where a single client and evaluation interface is desired, but where the flag data should come from more than one source.
+
+Some examples:
+
+- A migration from one feature flagging provider to another.
+  During that process, you may have some flags that have been ported to the new system and others that haven’t.
+  Therefore, you’d want the Multi-Provider to return the result of the “new” system if available otherwise, return the "old" system’s result.
+- Long-term use of multiple sources for flags.
+  For example, someone might want to be able to combine environment variables, database entries, and vendor feature flag results together in a single interface, and define the precedence order in which those sources should be consulted.
+- Setting a fallback for cloud providers.
+  You can use the Multi-Provider to automatically fall back to a local configuration if an external vendor provider goes down, rather than using the default values.
+  By using the FirstSuccessfulStrategy, the Multi-Provider will move on to the next provider in the list if an error is thrown.
+
+## Strategies
+
+The Multi-Provider supports multiple ways of deciding how to evaluate the set of providers it is managing, and how to deal with any errors that are thrown.
+
+Strategies must be adaptable to the various requirements that might be faced in a multi-provider situation.
+In some cases, the strategy may want to ignore errors from individual providers as long as one of them successfully responds.
+In other cases, it may want to evaluate providers in order and skip the rest if a successful result is obtained.
+In still other scenarios, it may be required to always call every provider and decide what to do with the set of results.
+
+The strategy to use is passed in to the Multi-Provider.
+
+By default, the Multi-Provider uses the “FirstMatchStrategy”.
+
+Here are some standard strategies that come with the Multi-Provider:
+
+### First Match
+
+Return the first result returned by a provider.
+Skip providers that indicate they had no value due to `FLAG_NOT_FOUND`.
+In all other cases, use the value returned by the provider.
+If any provider returns an error result other than `FLAG_NOT_FOUND`, the whole evaluation should error and “bubble up” the individual provider’s error in the result.
+
+As soon as a value is returned by a provider, the rest of the operation should short-circuit and not call the rest of the providers.
+
+### First Successful
+
+Similar to “First Match”, except that errors from evaluated providers do not halt execution.
+Instead, it will return the first successful result from a provider. If no provider successfully responds, it will throw an error result.
+
+### User Defined
+
+Rather than making assumptions about when to use a provider’s result and when not to (which may not hold across all providers) there is also a way for the user to define their own strategy that determines whether to use a result or fall through to the next one.
+
+## Installation
+
+<!-- x-release-please-start-version -->
+
+```xml
+
+<dependency>
+    <groupId>dev.openfeature.contrib.providers</groupId>
+    <artifactId>multi-provider</artifactId>
+    <version>0.0.1</version>
+</dependency>
+```
+
+<!-- x-release-please-end-version -->
+
+## Usage
+
+Usage example:
+
+```
+...
+List<FeatureProvider> providers = new ArrayList<>(2);
+providers.add(provider1);
+providers.add(provider2);
+
+// initialize using default strategy (first match)
+MultiProvider multiProvider = new MultiProvider(providers);
+OpenFeatureAPI.getInstance().setProviderAndWait(multiProvider);
+
+// initialize using a different strategy
+multiProvider = new MultiProvider(providers, new FirstSuccessfulStrategy());
+...
+```
+
+See [MultiProviderTest](./src/test/java/dev/openfeature/contrib/providers/multiprovider/MultiProviderTest.java)
+for more information.
+
diff --git a/providers/multiprovider/lombok.config b/providers/multiprovider/lombok.config
new file mode 100644
index 000000000..bcd1afdae
--- /dev/null
+++ b/providers/multiprovider/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/multiprovider/pom.xml b/providers/multiprovider/pom.xml
new file mode 100644
index 000000000..1ef8c52a2
--- /dev/null
+++ b/providers/multiprovider/pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>dev.openfeature.contrib</groupId>
+        <artifactId>parent</artifactId>
+        <version>0.1.0</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <groupId>dev.openfeature.contrib.providers</groupId>
+    <artifactId>multiprovider</artifactId>
+    <version>0.0.1</version> <!--x-release-please-version -->
+
+    <name>multiprovider</name>
+    <description>OpenFeature Multi-Provider</description>
+    <url>https://github.com/open-feature/java-sdk-contrib/tree/main/providers/multiprovider</url>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.json</groupId>
+            <artifactId>json</artifactId>
+            <version>20240303</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-slf4j2-impl</artifactId>
+            <version>2.24.1</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstMatchStrategy.java b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstMatchStrategy.java
new file mode 100644
index 000000000..3c90b3154
--- /dev/null
+++ b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstMatchStrategy.java
@@ -0,0 +1,53 @@
+package dev.openfeature.contrib.providers.multiprovider;
+
+import dev.openfeature.sdk.EvaluationContext;
+import dev.openfeature.sdk.FeatureProvider;
+import dev.openfeature.sdk.ProviderEvaluation;
+import dev.openfeature.sdk.exceptions.FlagNotFoundError;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+import java.util.function.Function;
+
+import static dev.openfeature.sdk.ErrorCode.FLAG_NOT_FOUND;
+
+/**
+ * First match strategy.
+ * Return the first result returned by a provider. Skip providers that indicate they had no value due to
+ * FLAG_NOT_FOUND.
+ * In all other cases, use the value returned by the provider.
+ * If any provider returns an error result other than FLAG_NOT_FOUND, the whole evaluation should error and
+ * “bubble up” the individual provider’s error in the result.
+ * As soon as a value is returned by a provider, the rest of the operation should short-circuit and not call
+ * the rest of the providers.
+ */
+@Slf4j
+@NoArgsConstructor
+public class FirstMatchStrategy implements Strategy {
+
+    /**
+     * Represents a strategy that evaluates providers based on a first-match approach.
+     * Provides a method to evaluate providers using a specified function and return the evaluation result.
+     *
+     * @param providerFunction provider function
+     * @param <T> ProviderEvaluation type
+     * @return the provider evaluation
+     */
+    @Override
+    public <T> ProviderEvaluation<T> evaluate(Map<String, FeatureProvider> providers, String key, T defaultValue,
+          EvaluationContext ctx, Function<FeatureProvider, ProviderEvaluation<T>> providerFunction) {
+        for (FeatureProvider provider: providers.values()) {
+            try {
+                ProviderEvaluation<T> res = providerFunction.apply(provider);
+                if (!FLAG_NOT_FOUND.equals(res.getErrorCode())) {
+                    return res;
+                }
+            } catch (FlagNotFoundError e) {
+                log.debug("flag not found {}", e.getMessage());
+            }
+        }
+
+        throw new FlagNotFoundError("flag not found");
+    }
+}
diff --git a/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java
new file mode 100644
index 000000000..ec23746fb
--- /dev/null
+++ b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java
@@ -0,0 +1,39 @@
+package dev.openfeature.contrib.providers.multiprovider;
+
+import dev.openfeature.sdk.EvaluationContext;
+import dev.openfeature.sdk.FeatureProvider;
+import dev.openfeature.sdk.ProviderEvaluation;
+import dev.openfeature.sdk.exceptions.GeneralError;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * First Successful Strategy.
+ * Similar to “First Match”, except that errors from evaluated providers do not halt execution.
+ * Instead, it will return the first successful result from a provider.
+ * If no provider successfully responds, it will throw an error result.
+ */
+@Slf4j
+@NoArgsConstructor
+public class FirstSuccessfulStrategy implements Strategy {
+
+    @Override
+    public <T> ProviderEvaluation<T> evaluate(Map<String, FeatureProvider> providers, String key, T defaultValue,
+          EvaluationContext ctx, Function<FeatureProvider, ProviderEvaluation<T>> providerFunction) {
+        for (FeatureProvider provider: providers.values()) {
+            try {
+                ProviderEvaluation<T> res = providerFunction.apply(provider);
+                if (res.getErrorCode() == null) {
+                    return res;
+                }
+            } catch (Exception e) {
+                log.debug("evaluation exception {}", e.getMessage());
+            }
+        }
+
+        throw new GeneralError("evaluation error");
+    }
+}
diff --git a/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java
new file mode 100644
index 000000000..65d24cb41
--- /dev/null
+++ b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java
@@ -0,0 +1,152 @@
+package dev.openfeature.contrib.providers.multiprovider;
+
+import dev.openfeature.sdk.EvaluationContext;
+import dev.openfeature.sdk.EventProvider;
+import dev.openfeature.sdk.FeatureProvider;
+import dev.openfeature.sdk.Metadata;
+import dev.openfeature.sdk.ProviderEvaluation;
+import dev.openfeature.sdk.Value;
+import dev.openfeature.sdk.exceptions.GeneralError;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * Provider implementation for Multi-provider.
+ */
+@Slf4j
+public class MultiProvider extends EventProvider {
+
+    @Getter
+    private static final String NAME = "multiprovider";
+    public static final int INIT_THREADS_COUNT = 8;
+    private final Map<String, FeatureProvider> providers;
+    private final Strategy strategy;
+    private String metadataName;
+
+    /**
+     * Constructs a MultiProvider with the given list of FeatureProviders, using a default strategy.
+     *
+     * @param providers the list of FeatureProviders to initialize the MultiProvider with
+     */
+    public MultiProvider(List<FeatureProvider> providers) {
+        this(providers, null);
+    }
+
+    /**
+     * Constructs a MultiProvider with the given list of FeatureProviders and a strategy.
+     *
+     * @param providers the list of FeatureProviders to initialize the MultiProvider with
+     * @param strategy the strategy
+     */
+    public MultiProvider(List<FeatureProvider> providers, Strategy strategy) {
+        this.providers = buildProviders(providers);
+        if (strategy != null) {
+            this.strategy = strategy;
+        } else {
+            this.strategy = new FirstMatchStrategy();
+        }
+    }
+
+    protected static Map<String, FeatureProvider> buildProviders(List<FeatureProvider> providers) {
+        Map<String, FeatureProvider> providersMap = new LinkedHashMap<>(providers.size());
+        for (FeatureProvider provider: providers) {
+            FeatureProvider prevProvider = providersMap.put(provider.getMetadata().getName(), provider);
+            if (prevProvider != null) {
+                log.warn("duplicated provider name: {}", provider.getMetadata().getName());
+            }
+        }
+        return Collections.unmodifiableMap(providersMap);
+    }
+
+    /**
+     * Initialize the provider.
+     * @param evaluationContext evaluation context
+     * @throws Exception on error
+     */
+    @Override
+    public void initialize(EvaluationContext evaluationContext) throws Exception {
+        JSONObject json = new JSONObject();
+        json.put("name", NAME);
+        JSONObject providersMetadata = new JSONObject();
+        json.put("originalMetadata", providersMetadata);
+        ExecutorService initPool = Executors.newFixedThreadPool(INIT_THREADS_COUNT);
+        Collection<Callable<Boolean>> tasks = new ArrayList<>(providers.size());
+        for (FeatureProvider provider: providers.values()) {
+            tasks.add(() -> {
+                provider.initialize(evaluationContext);
+                return true;
+            });
+            JSONObject providerMetadata = new JSONObject();
+            providerMetadata.put("name", provider.getMetadata().getName());
+            providersMetadata.put(provider.getMetadata().getName(), providerMetadata);
+        }
+        List<Future<Boolean>> results = initPool.invokeAll(tasks);
+        for (Future<Boolean> result: results) {
+            if (!result.get()) {
+                throw new GeneralError("init failed");
+            }
+        }
+        metadataName = json.toString();
+    }
+
+    @Override
+    public Metadata getMetadata() {
+        return () -> metadataName;
+    }
+
+    @Override
+    public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
+        return strategy.evaluate(providers, key, defaultValue, ctx,
+            p -> p.getBooleanEvaluation(key, defaultValue, ctx));
+    }
+
+    @Override
+    public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
+        return strategy.evaluate(providers, key, defaultValue, ctx,
+            p -> p.getStringEvaluation(key, defaultValue, ctx));
+    }
+
+    @Override
+    public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
+        return strategy.evaluate(providers, key, defaultValue, ctx,
+            p -> p.getIntegerEvaluation(key, defaultValue, ctx));
+    }
+
+    @Override
+    public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
+        return strategy.evaluate(providers, key, defaultValue, ctx,
+            p -> p.getDoubleEvaluation(key, defaultValue, ctx));
+    }
+
+    @Override
+    public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) {
+        return strategy.evaluate(providers, key, defaultValue, ctx,
+            p -> p.getObjectEvaluation(key, defaultValue, ctx));
+    }
+
+    @Override
+    public void shutdown() {
+        log.debug("shutdown begin");
+        for (FeatureProvider provider: providers.values()) {
+            try {
+                provider.shutdown();
+            } catch (Exception e) {
+                log.error("error shutdown provider {}", provider.getMetadata().getName(), e);
+            }
+        }
+        log.debug("shutdown end");
+    }
+
+}
diff --git a/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java
new file mode 100644
index 000000000..85fc06747
--- /dev/null
+++ b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java
@@ -0,0 +1,16 @@
+package dev.openfeature.contrib.providers.multiprovider;
+
+import dev.openfeature.sdk.EvaluationContext;
+import dev.openfeature.sdk.FeatureProvider;
+import dev.openfeature.sdk.ProviderEvaluation;
+
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * strategy.
+ */
+public interface Strategy {
+    <T> ProviderEvaluation<T> evaluate(Map<String, FeatureProvider> providers, String key, T defaultValue,
+        EvaluationContext ctx, Function<FeatureProvider, ProviderEvaluation<T>> providerFunction);
+}
diff --git a/providers/multiprovider/src/test/java/dev/openfeature/contrib/providers/multiprovider/MultiProviderTest.java b/providers/multiprovider/src/test/java/dev/openfeature/contrib/providers/multiprovider/MultiProviderTest.java
new file mode 100644
index 000000000..943728c6a
--- /dev/null
+++ b/providers/multiprovider/src/test/java/dev/openfeature/contrib/providers/multiprovider/MultiProviderTest.java
@@ -0,0 +1,202 @@
+package dev.openfeature.contrib.providers.multiprovider;
+
+import dev.openfeature.sdk.EvaluationContext;
+import dev.openfeature.sdk.FeatureProvider;
+import dev.openfeature.sdk.Metadata;
+import dev.openfeature.sdk.MutableContext;
+import dev.openfeature.sdk.ProviderEvaluation;
+import dev.openfeature.sdk.Value;
+import dev.openfeature.sdk.exceptions.FlagNotFoundError;
+import dev.openfeature.sdk.exceptions.GeneralError;
+import dev.openfeature.sdk.providers.memory.Flag;
+import dev.openfeature.sdk.providers.memory.InMemoryProvider;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class MultiProviderTest {
+
+    @SneakyThrows
+    @Test
+    public void testInit() {
+        FeatureProvider provider1 = mock(FeatureProvider.class);
+        FeatureProvider provider2 = mock(FeatureProvider.class);
+        when(provider1.getMetadata()).thenReturn(() -> "provider1");
+        when(provider2.getMetadata()).thenReturn(() -> "provider2");
+
+        List<FeatureProvider> providers = new ArrayList<>(2);
+        providers.add(provider1);
+        providers.add(provider2);
+        Strategy strategy = mock(Strategy.class);
+        MultiProvider multiProvider = new MultiProvider(providers, strategy);
+        multiProvider.initialize(null);
+
+        assertNotNull(multiProvider);
+        assertEquals("{\"originalMetadata\":{\"provider1\":{\"name\":\"provider1\"}," +
+            "\"provider2\":{\"name\":\"provider2\"}},\"name\":\"multiprovider\"}",
+                multiProvider.getMetadata().getName());
+    }
+
+    @SneakyThrows
+    @Test
+    public void testInitOneFails() {
+        FeatureProvider provider1 = mock(FeatureProvider.class);
+        FeatureProvider provider2 = mock(FeatureProvider.class);
+        when(provider1.getMetadata()).thenReturn(() -> "provider1");
+        when(provider2.getMetadata()).thenReturn(() -> "provider2");
+        doThrow(new GeneralError()).when(provider1).initialize(any());
+        doThrow(new GeneralError()).when(provider1).shutdown();
+
+        List<FeatureProvider> providers = new ArrayList<>(2);
+        providers.add(provider1);
+        providers.add(provider2);
+        Strategy strategy = mock(Strategy.class);
+        MultiProvider multiProvider = new MultiProvider(providers, strategy);
+        assertThrows(ExecutionException.class, () -> multiProvider.initialize(null));
+        assertDoesNotThrow(() -> multiProvider.shutdown());
+    }
+
+    @Test
+    public void testDuplicateProviderNames() {
+        FeatureProvider provider1 = mock(FeatureProvider.class);
+        FeatureProvider provider2 = mock(FeatureProvider.class);
+        when(provider1.getMetadata()).thenReturn(() -> "provider");
+        when(provider2.getMetadata()).thenReturn(() -> "provider");
+
+        List<FeatureProvider> providers = new ArrayList<>(2);
+        providers.add(provider1);
+        providers.add(provider2);
+
+        assertDoesNotThrow(() -> new MultiProvider(providers, null).initialize(null));
+    }
+
+    @SneakyThrows
+    @Test
+    public void testRetrieveMetadataName() {
+        List<FeatureProvider> providers = new ArrayList<>();
+        FeatureProvider mockProvider = mock(FeatureProvider.class);
+        when(mockProvider.getMetadata()).thenReturn(() -> "MockProvider");
+        providers.add(mockProvider);
+        Strategy mockStrategy = mock(Strategy.class);
+        MultiProvider multiProvider = new MultiProvider(providers, mockStrategy);
+        multiProvider.initialize(null);
+
+        assertEquals("{\"originalMetadata\":{\"MockProvider\":{\"name\":\"MockProvider\"}}," +
+            "\"name\":\"multiprovider\"}", multiProvider.getMetadata().getName());
+    }
+
+    @SneakyThrows
+    @Test
+    public void testEvaluations() {
+        Map<String, Flag<?>> flags1 = new HashMap<>();
+        flags1.put("b1", Flag.builder().variant("true", true)
+            .variant("false", false).defaultVariant("true").build());
+        flags1.put("i1", Flag.builder().variant("v", 1).defaultVariant("v").build());
+        flags1.put("d1", Flag.builder().variant("v", 1.0).defaultVariant("v").build());
+        flags1.put("s1", Flag.builder().variant("v", "str1").defaultVariant("v").build());
+        flags1.put("o1", Flag.builder().variant("v", new Value("v1"))
+            .defaultVariant("v").build());
+        InMemoryProvider provider1 = new InMemoryProvider(flags1) {
+            public Metadata getMetadata() {
+                return () -> "old-provider";
+            }
+        };
+        Map<String, Flag<?>> flags2 = new HashMap<>();
+        flags2.put("b1", Flag.builder().variant("true", true)
+            .variant("false", false).defaultVariant("false").build());
+        flags2.put("i1", Flag.builder().variant("v", 2).defaultVariant("v").build());
+        flags2.put("d1", Flag.builder().variant("v", 2.0).defaultVariant("v").build());
+        flags2.put("s1", Flag.builder().variant("v", "str2").defaultVariant("v").build());
+        flags2.put("o1", Flag.builder().variant("v", new Value("v2"))
+                .defaultVariant("v").build());
+
+        flags2.put("s2", Flag.builder().variant("v", "s2str2").defaultVariant("v").build());
+        InMemoryProvider provider2 = new InMemoryProvider(flags2) {
+            public Metadata getMetadata() {
+                return () -> "new-provider";
+            }
+        };
+        List<FeatureProvider> providers = new ArrayList<>(2);
+        providers.add(provider1);
+        providers.add(provider2);
+        MultiProvider multiProvider = new MultiProvider(providers);
+        multiProvider.initialize(null);
+
+        assertEquals(true, multiProvider.getBooleanEvaluation("b1", false, null)
+            .getValue());
+        assertEquals(1, multiProvider.getIntegerEvaluation("i1", 0, null)
+            .getValue());
+        assertEquals(1.0, multiProvider.getDoubleEvaluation("d1", 0.0, null)
+            .getValue());
+        assertEquals("str1", multiProvider.getStringEvaluation("s1", "", null)
+            .getValue());
+        assertEquals("v1", multiProvider.getObjectEvaluation("o1", null, null)
+            .getValue().asString());
+
+        assertEquals("s2str2", multiProvider.getStringEvaluation("s2", "", null)
+            .getValue());
+        MultiProvider finalMultiProvider1 = multiProvider;
+        assertThrows(FlagNotFoundError.class, () ->
+            finalMultiProvider1.getStringEvaluation("non-existing", "", null));
+
+        multiProvider.shutdown();
+        multiProvider = new MultiProvider(providers, new FirstSuccessfulStrategy());
+        multiProvider.initialize(null);
+
+        assertEquals(true, multiProvider.getBooleanEvaluation("b1", false, null)
+            .getValue());
+        assertEquals(1, multiProvider.getIntegerEvaluation("i1", 0, null)
+            .getValue());
+        assertEquals(1.0, multiProvider.getDoubleEvaluation("d1", 0.0, null)
+            .getValue());
+        assertEquals("str1", multiProvider.getStringEvaluation("s1", "", null)
+            .getValue());
+        assertEquals("v1", multiProvider.getObjectEvaluation("o1", null, null)
+            .getValue().asString());
+
+        assertEquals("s2str2", multiProvider.getStringEvaluation("s2", "", null)
+            .getValue());
+        MultiProvider finalMultiProvider2 = multiProvider;
+        assertThrows(GeneralError.class, () ->
+            finalMultiProvider2.getStringEvaluation("non-existing", "", null));
+
+        multiProvider.shutdown();
+        Strategy customStrategy = new Strategy() {
+            final FirstMatchStrategy fallbackStrategy = new FirstMatchStrategy();
+            @Override
+            public <T> ProviderEvaluation<T> evaluate(Map<String, FeatureProvider> providers, String key, T defaultValue, EvaluationContext ctx, Function<FeatureProvider, ProviderEvaluation<T>> providerFunction) {
+                Value contextProvider = null;
+                if (ctx != null) {
+                    contextProvider = ctx.getValue("provider");
+                }
+                if (contextProvider != null && "new-provider".equals(contextProvider.asString())) {
+                    return providerFunction.apply(providers.get("new-provider"));
+                }
+                return fallbackStrategy.evaluate(providers, key, defaultValue, ctx, providerFunction);
+            }
+        };
+        multiProvider = new MultiProvider(providers, customStrategy);
+        multiProvider.initialize(null);
+
+        EvaluationContext context = new MutableContext().add("provider", "new-provider");
+        assertEquals(false, multiProvider.getBooleanEvaluation("b1", true, context)
+            .getValue());
+        assertEquals(true, multiProvider.getBooleanEvaluation("b1", true, null)
+            .getValue());
+    }
+}
\ No newline at end of file
diff --git a/providers/multiprovider/src/test/resources/log4j2-test.xml b/providers/multiprovider/src/test/resources/log4j2-test.xml
new file mode 100644
index 000000000..aced30f8a
--- /dev/null
+++ b/providers/multiprovider/src/test/resources/log4j2-test.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="INFO">
+    <Appenders>
+        <Console name="consoleLogger" target="SYSTEM_OUT">
+            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
+        </Console>
+    </Appenders>
+    <Loggers>
+        <Root level="INFO">
+            <AppenderRef ref="consoleLogger"/>
+        </Root>
+    </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/providers/multiprovider/version.txt b/providers/multiprovider/version.txt
new file mode 100644
index 000000000..8acdd82b7
--- /dev/null
+++ b/providers/multiprovider/version.txt
@@ -0,0 +1 @@
+0.0.1
diff --git a/release-please-config.json b/release-please-config.json
index 964a4ee8b..b8a017625 100644
--- a/release-please-config.json
+++ b/release-please-config.json
@@ -100,6 +100,17 @@
         "README.md"
       ]
     },
+    "providers/multiprovider": {
+      "package-name": "dev.openfeature.contrib.providers.multiprovider",
+      "release-type": "simple",
+      "bump-minor-pre-major": true,
+      "bump-patch-for-minor-pre-major": true,
+      "versioning": "default",
+      "extra-files": [
+        "pom.xml",
+        "README.md"
+      ]
+    },
     "hooks/open-telemetry": {
       "package-name": "dev.openfeature.contrib.hooks.otel",
       "release-type": "simple",
