Repository: open-feature/java-sdk
Pull request #1049 (2024-08-15 17:27:15)

fix: updated context not passed to all hooks
Fixes an issue where the fully merged and updated context was used in the evaluation, but not in all hooks.

Fixes: https://github.com/open-feature/java-sdk/issues/1047

sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049)


toddbaert: @UtkarshSharma2612 feel free to add yourself as a reviewer if you want :pray: 
sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/open-feature/java-sdk/pull/1049?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 95.28%. Comparing base [(`00bc060`)](https://app.codecov.io/gh/open-feature/java-sdk/commit/00bc0609eb270e15129a3ebadb8671e61603b865?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) to head [(`74d2189`)](https://app.codecov.io/gh/open-feature/java-sdk/commit/74d2189c6d97e64829129a15da51320f02fae6e7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature).

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##               main    #1049      +/-   ##
============================================
+ Coverage     95.04%   95.28%   +0.23%     
- Complexity      392      394       +2     
============================================
  Files            38       38              
  Lines           888      890       +2     
  Branches         54       54              
============================================
+ Hits            844      848       +4     
+ Misses           24       23       -1     
+ Partials         20       19       -1     
```

| [Flag](https://app.codecov.io/gh/open-feature/java-sdk/pull/1049/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/open-feature/java-sdk/pull/1049/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) | `95.28% <100.00%> (+0.23%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/open-feature/java-sdk/pull/1049?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature).

sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=1049&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=1049&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1049)



Patch:
diff --git a/src/main/java/dev/openfeature/sdk/ImmutableContext.java b/src/main/java/dev/openfeature/sdk/ImmutableContext.java
index 0d02ba3..5700998 100644
--- a/src/main/java/dev/openfeature/sdk/ImmutableContext.java
+++ b/src/main/java/dev/openfeature/sdk/ImmutableContext.java
@@ -73,7 +73,7 @@ public final class ImmutableContext implements EvaluationContext {
      * Merges this EvaluationContext object with the passed EvaluationContext, overriding in case of conflict.
      *
      * @param overridingContext overriding context
-     * @return resulting merged context
+     * @return new, resulting merged context
      */
     @Override
     public EvaluationContext merge(EvaluationContext overridingContext) {
diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
index 4a1c117..962f7d9 100644
--- a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
+++ b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
@@ -105,7 +105,7 @@ public class OpenFeatureClient implements Client {
 
         FlagEvaluationDetails<T> details = null;
         List<Hook> mergedHooks = null;
-        HookContext<T> hookCtx = null;
+        HookContext<T> afterHookContext = null;
         FeatureProvider provider;
 
         try {
@@ -115,12 +115,11 @@ public class OpenFeatureClient implements Client {
             mergedHooks = ObjectUtils.merge(provider.getProviderHooks(), flagOptions.getHooks(), clientHooks,
                     openfeatureApi.getHooks());
 
-            hookCtx = HookContext.from(key, type, this.getMetadata(),
-                    provider.getMetadata(), ctx, defaultValue);
+            EvaluationContext mergedCtx = hookSupport.beforeHooks(type, HookContext.from(key, type, this.getMetadata(),
+                    provider.getMetadata(), mergeEvaluationContext(ctx), defaultValue), mergedHooks, hints);
 
-            EvaluationContext ctxFromHook = hookSupport.beforeHooks(type, hookCtx, mergedHooks, hints);
-
-            EvaluationContext mergedCtx = mergeEvaluationContext(ctxFromHook, ctx);
+            afterHookContext = HookContext.from(key, type, this.getMetadata(),
+                    provider.getMetadata(), mergedCtx, defaultValue);
 
             ProviderEvaluation<T> providerEval = (ProviderEvaluation<T>) createProviderEvaluation(type, key,
                     defaultValue, provider, mergedCtx);
@@ -129,7 +128,7 @@ public class OpenFeatureClient implements Client {
             if (details.getErrorCode() != null) {
                 throw ExceptionUtils.instantiateErrorByErrorCode(details.getErrorCode(), details.getErrorMessage());
             } else {
-                hookSupport.afterHooks(type, hookCtx, details, mergedHooks, hints);
+                hookSupport.afterHooks(type, afterHookContext, details, mergedHooks, hints);
             }
         } catch (Exception e) {
             log.error("Unable to correctly evaluate flag with key '{}'", key, e);
@@ -144,24 +143,22 @@ public class OpenFeatureClient implements Client {
             details.setErrorMessage(e.getMessage());
             details.setValue(defaultValue);
             details.setReason(Reason.ERROR.toString());
-            hookSupport.errorHooks(type, hookCtx, e, mergedHooks, hints);
+            hookSupport.errorHooks(type, afterHookContext, e, mergedHooks, hints);
         } finally {
-            hookSupport.afterAllHooks(type, hookCtx, mergedHooks, hints);
+            hookSupport.afterAllHooks(type, afterHookContext, mergedHooks, hints);
         }
 
         return details;
     }
 
     /**
-     * Merge hook and invocation contexts with API, transaction and client contexts.
+     * Merge invocation contexts with API, transaction and client contexts.
+     * Does not merge before context.
      *
-     * @param hookContext       hook context
      * @param invocationContext invocation context
      * @return merged evaluation context
      */
-    private EvaluationContext mergeEvaluationContext(
-            EvaluationContext hookContext,
-            EvaluationContext invocationContext) {
+    private EvaluationContext mergeEvaluationContext(EvaluationContext invocationContext) {
         final EvaluationContext apiContext = openfeatureApi.getEvaluationContext() != null
                 ? openfeatureApi.getEvaluationContext()
                 : new ImmutableContext();
@@ -172,7 +169,7 @@ public class OpenFeatureClient implements Client {
                 ? openfeatureApi.getTransactionContext()
                 : new ImmutableContext();
 
-        return apiContext.merge(transactionContext.merge(clientContext.merge(invocationContext.merge(hookContext))));
+        return apiContext.merge(transactionContext.merge(clientContext.merge(invocationContext)));
     }
 
     private <T> ProviderEvaluation<?> createProviderEvaluation(


Test patch:
diff --git a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
index b2a7510..6d2bca3 100644
--- a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
+++ b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
@@ -2,7 +2,6 @@ package dev.openfeature.sdk;
 
 import static dev.openfeature.sdk.DoSomethingProvider.DEFAULT_METADATA;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -20,6 +19,7 @@ import static org.mockito.Mockito.verify;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import org.awaitility.Awaitility;
 import org.junit.jupiter.api.AfterEach;
@@ -336,11 +336,25 @@ class FlagEvaluationSpecTest implements HookFixtures {
         FeatureProviderTestUtils.setFeatureProvider(provider);
         TransactionContextPropagator transactionContextPropagator = new ThreadLocalTransactionContextPropagator();
         api.setTransactionContextPropagator(transactionContextPropagator);
+        Hook<Boolean> hook = spy(new Hook<Boolean>() {
+            @Override
+            public Optional<EvaluationContext> before(HookContext<Boolean> ctx, Map<String, Object> hints) {
+                Map<String, Value> attrs = ctx.getCtx().asMap();
+                attrs.put("before", new Value("5"));
+                attrs.put("common7", new Value("5"));
+                return Optional.ofNullable(new ImmutableContext(attrs));
+            }
+            @Override
+            public void after(HookContext<Boolean> ctx, FlagEvaluationDetails<Boolean> details, Map<String, Object> hints) {
+                Hook.super.after(ctx, details, hints);
+            }
+        });
 
         Map<String, Value> apiAttributes = new HashMap<>();
         apiAttributes.put("common1", new Value("1"));
         apiAttributes.put("common2", new Value("1"));
         apiAttributes.put("common3", new Value("1"));
+        apiAttributes.put("common7", new Value("1"));
         apiAttributes.put("api", new Value("1"));
         EvaluationContext apiCtx = new ImmutableContext(apiAttributes);
 
@@ -377,21 +391,55 @@ class FlagEvaluationSpecTest implements HookFixtures {
         invocationAttributes.put("invocation", new Value("4"));
         EvaluationContext invocationCtx = new ImmutableContext(invocationAttributes);
 
-        c.getBooleanValue("key", false, invocationCtx);
-
-        // assert the connect overrides
+        c.getBooleanValue("key", false, invocationCtx, FlagEvaluationOptions.builder().hook(hook).build());
+
+        // assert the correct overrides in before hook
+        verify(hook).before(argThat((arg) -> {
+            EvaluationContext evaluationContext = arg.getCtx();
+            return evaluationContext.getValue("api").asString().equals("1") &&
+                    evaluationContext.getValue("transaction").asString().equals("2") &&
+                    evaluationContext.getValue("client").asString().equals("3") &&
+                    evaluationContext.getValue("invocation").asString().equals("4") &&
+                    evaluationContext.getValue("common1").asString().equals("2") &&
+                    evaluationContext.getValue("common2").asString().equals("3") &&
+                    evaluationContext.getValue("common3").asString().equals("4") &&
+                    evaluationContext.getValue("common4").asString().equals("3") &&
+                    evaluationContext.getValue("common5").asString().equals("4") &&
+                    evaluationContext.getValue("common6").asString().equals("4");
+        }), any());
+
+        // assert the correct overrides in evaluation
         verify(provider).getBooleanEvaluation(any(), any(), argThat((arg) -> {
             return arg.getValue("api").asString().equals("1") &&
                     arg.getValue("transaction").asString().equals("2") &&
                     arg.getValue("client").asString().equals("3") &&
                     arg.getValue("invocation").asString().equals("4") &&
+                    arg.getValue("before").asString().equals("5") &&
                     arg.getValue("common1").asString().equals("2") &&
                     arg.getValue("common2").asString().equals("3") &&
                     arg.getValue("common3").asString().equals("4") &&
                     arg.getValue("common4").asString().equals("3") &&
                     arg.getValue("common5").asString().equals("4") &&
-                    arg.getValue("common6").asString().equals("4");
+                    arg.getValue("common6").asString().equals("4") &&
+                    arg.getValue("common7").asString().equals("5");
         }));
+
+        // assert the correct overrides in after hook
+        verify(hook).after(argThat((arg) -> {
+            EvaluationContext evaluationContext = arg.getCtx();
+            return evaluationContext.getValue("api").asString().equals("1") &&
+                    evaluationContext.getValue("transaction").asString().equals("2") &&
+                    evaluationContext.getValue("client").asString().equals("3") &&
+                    evaluationContext.getValue("invocation").asString().equals("4") &&
+                    evaluationContext.getValue("before").asString().equals("5") &&
+                    evaluationContext.getValue("common1").asString().equals("2") &&
+                    evaluationContext.getValue("common2").asString().equals("3") &&
+                    evaluationContext.getValue("common3").asString().equals("4") &&
+                    evaluationContext.getValue("common4").asString().equals("3") &&
+                    evaluationContext.getValue("common5").asString().equals("4") &&
+                    evaluationContext.getValue("common6").asString().equals("4") &&
+                    evaluationContext.getValue("common7").asString().equals("5");
+        }), any(), any());
     }
 
     @Specification(number="3.3.1.1", text="The API SHOULD have a method for setting a transaction context propagator.")
diff --git a/src/test/java/dev/openfeature/sdk/HookSpecTest.java b/src/test/java/dev/openfeature/sdk/HookSpecTest.java
index 098d80d..2554457 100644
--- a/src/test/java/dev/openfeature/sdk/HookSpecTest.java
+++ b/src/test/java/dev/openfeature/sdk/HookSpecTest.java
@@ -484,10 +484,11 @@ class HookSpecTest implements HookFixtures {
     @Specification(number = "4.1.4", text = "The evaluation context MUST be mutable only within the before hook.")
     @Specification(number = "4.3.4", text = "Any `evaluation context` returned from a `before` hook MUST be passed to subsequent `before` hooks (via `HookContext`).")
     @Test void beforeContextUpdated() {
-        EvaluationContext ctx = new ImmutableContext();
-        Hook hook = mockBooleanHook();
+        String targetingKey = "test-key";
+        EvaluationContext ctx = new ImmutableContext(targetingKey);
+        Hook<Boolean> hook = mockBooleanHook();
         when(hook.before(any(), any())).thenReturn(Optional.of(ctx));
-        Hook hook2 = mockBooleanHook();
+        Hook<Boolean> hook2 = mockBooleanHook();
         when(hook.before(any(), any())).thenReturn(Optional.empty());
         InOrder order = inOrder(hook, hook2);
 
@@ -499,11 +500,11 @@ class HookSpecTest implements HookFixtures {
                         .build());
 
         order.verify(hook).before(any(), any());
-        ArgumentCaptor<HookContext> captor = ArgumentCaptor.forClass(HookContext.class);
+        ArgumentCaptor<HookContext<Boolean>> captor = ArgumentCaptor.forClass(HookContext.class);
         order.verify(hook2).before(captor.capture(), any());
 
-        HookContext hc = captor.getValue();
-        assertEquals(hc.getCtx(), ctx);
+        HookContext<Boolean> hc = captor.getValue();
+        assertEquals(hc.getCtx().getTargetingKey(), targetingKey);
 
     }