Repository: open-feature/java-sdk
Pull request #1096 (2024-09-09 14:01:16)

feat: make provider interface "stateless"; SDK maintains provider state
## This PR

Providers no longer maintain their own state: the state for each provider is maintained in the SDK automatically, and updated according to the success/failures of lifecycle methods (init/shutdown) or events emitted from providers spontaneously.

### Related Issues

https://github.com/open-feature/java-sdk/issues/844

Please remove `getState` in implementing providers (the interface method has been marked as deprecated).

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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [5 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [5 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


toddbaert: Thanks a lot for this! I will try to review tomorrow! :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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [5 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [5 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/open-feature/java-sdk/pull/1096?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) Report
Attention: Patch coverage is `92.30769%` with `4 lines` in your changes missing coverage. Please review.
> Project coverage is 95.24%. Comparing base [(`6408261`)](https://app.codecov.io/gh/open-feature/java-sdk/commit/64082617fade3fa7d647302cc0c5b698f7038d81?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) to head [(`b993ee2`)](https://app.codecov.io/gh/open-feature/java-sdk/commit/b993ee21bae9eba5938c7b9034624eba911a846d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature).

| [Files with missing lines](https://app.codecov.io/gh/open-feature/java-sdk/pull/1096?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) | Patch % | Lines |
|---|---|---|
| [...feature/sdk/providers/memory/InMemoryProvider.java](https://app.codecov.io/gh/open-feature/java-sdk/pull/1096?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fdev%2Fopenfeature%2Fsdk%2Fproviders%2Fmemory%2FInMemoryProvider.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature#diff-c3JjL21haW4vamF2YS9kZXYvb3BlbmZlYXR1cmUvc2RrL3Byb3ZpZGVycy9tZW1vcnkvSW5NZW1vcnlQcm92aWRlci5qYXZh) | 78.57% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/open-feature/java-sdk/pull/1096?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) |
| [...in/java/dev/openfeature/sdk/OpenFeatureClient.java](https://app.codecov.io/gh/open-feature/java-sdk/pull/1096?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fdev%2Fopenfeature%2Fsdk%2FOpenFeatureClient.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature#diff-c3JjL21haW4vamF2YS9kZXYvb3BlbmZlYXR1cmUvc2RrL09wZW5GZWF0dXJlQ2xpZW50LmphdmE=) | 87.50% | [1 Missing :warning: ](https://app.codecov.io/gh/open-feature/java-sdk/pull/1096?src=pr&el=tree&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    #1096      +/-   ##
============================================
+ Coverage     95.09%   95.24%   +0.15%     
- Complexity      400      414      +14     
============================================
  Files            39       39              
  Lines           917      947      +30     
  Branches         56       63       +7     
============================================
+ Hits            872      902      +30     
- Misses           24       26       +2     
+ Partials         21       19       -2     
```

| [Flag](https://app.codecov.io/gh/open-feature/java-sdk/pull/1096/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/1096/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) | `95.24% <92.30%> (+0.15%)` | :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/1096?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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [5 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [5 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [11 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


sonarcloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [D Reliability Rating on New Code](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096) (required ≥ A)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)

##   
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb-16px.png '') Catch issues before they fail your Quality Gate with our IDE extension ![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint-16px.png '') [SonarLint](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=pull-request)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


toddbaert: You have some checkstyle violations: https://github.com/open-feature/java-sdk/actions/runs/10825623982/job/30071997539?pr=1096

You can see these (and more) by running `mvn verify` locally.
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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [10 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


toddbaert: > Not sure if we want to have SDK side state machine mandatory.
> 
> If yes, the wrapper concept makes sense. However, this approach is a little complex to implement and there are some downsides:
> 
> * `Provider.getState()` does not really reflect the provider state anymore
> * If someone gets the hands on a `FeatureProvider` which is not a `FeatureProviderWrapper` or a `EventProvider` and calls a method that should cause a state transition, this transition will not be seen by the SDK
> 
> I would prefer an opt-in approach, where providers can outsource state management to the SDK but don't have to.
> 
> What do you think?

@guidobrei 

The init State machine is mandatory now as it's in the OpenFeature spec. This sdk is one of the last implementations not to have it. We've had lots of issues in the past with providers being confused about updating this state; this makes the old state useless, yes, but it's easily removed.

There's a blog post about why the community decided on this change here: https://openfeature.dev/blog/reconciling-with-state
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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [10 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


guidobrei: > The init State machine is mandatory now as it's in the OpenFeature spec.

@toddbaert 

If we have now a breaking change in the spec, why do we want to do a non-breaking change in the SDK?
The way the Java SDK is built I see little hope to build it clean and non-breaking. 

@chrfwow approach with wrapping all incoming providers in a `StatefulFeatureProvider` seems promising. However, it still has some breaking behavior: Whatever FeatureProvider you add to the SDK, you get a StatefulFeatureProvider when accessing it again. 

Just adding the wrapper as backwards compatible implementation feels a like a workaround. We should go for a proper solution with an additional workaround for existing providers.
We could make the `StatefulFeatureProvider` public abstract and put it in the inheritance chain of our known Providers by making it the base class of all Provider implementations, and additionally ask other implementers to inherit from this class from now on. Maybe we need some additional `StatefulEventProvider`, as we have to ensure that `initialize()`, `shutdown()`... are called on the base class. 
In the meanwhile, we can stay backwards compatible, by wrapping all other implementations that directly implement `FeatureProvider` into a `StatefulFeatureProviderWrapper` (the current implementation by @chrfwow ). This way we minimize the current limitation of not having access to the actual provider that was added to SDK. 
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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [10 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


chrfwow: > However, it still has some breaking behavior: Whatever FeatureProvider you add to the SDK, you get a StatefulFeatureProvider when accessing it again.
I could correct this behaviour by returning the delegate in the `ProviderRepository.getProvider()` method as in the [latest commit](https://github.com/open-feature/java-sdk/pull/1096/commits/4bc6f664d2efc3db90aa2184f33a66f31b596b74)
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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [10 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [9 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [9 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [9 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [7 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


toddbaert: @chrfwow I think I agree with @guidobrei 's points. Thanks for being so patient with all these reviews! I think we're almost there but I think the last few changes seem small.
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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [7 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [7 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [7 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)


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=1096) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [7 New issues](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=1096&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=1096&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=1096&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=1096&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=1096&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=1096)



Patch:
diff --git a/README.md b/README.md
index 920e55f..32ddccf 100644
--- a/README.md
+++ b/README.md
@@ -317,11 +317,6 @@ public class MyProvider implements FeatureProvider {
         return () -> "My Provider";
     }
 
-    @Override
-    public ProviderState getState() {
-        // optionally indicate your provider's state (assumed to be READY if not implemented)
-    }
-
     @Override
     public void initialize(EvaluationContext evaluationContext) throws Exception {
         // start up your provider
@@ -368,11 +363,6 @@ class MyEventProvider extends EventProvider {
         return () -> "My Event Provider";
     }
 
-    @Override
-    public ProviderState getState() {
-        // indicate your provider's state (required for EventProviders)
-    }
-
     @Override
     public void initialize(EvaluationContext evaluationContext) throws Exception {
         // emit events when flags are changed in a hypothetical REST API
@@ -391,6 +381,13 @@ class MyEventProvider extends EventProvider {
 }
 ```
 
+Providers no longer need to manage their own state, this is done by the SDK itself. If desired, the state of a provider 
+can be queried through the client that uses the provider.
+
+```java
+OpenFeatureAPI.getInstance().getClient().getProviderState();
+```
+
 > Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=&labels=provider&projects=&template=document-provider.yaml&title=%5BProvider%5D%3A+) so we can add it to the docs!
 
 ### Develop a hook
diff --git a/src/main/java/dev/openfeature/sdk/Client.java b/src/main/java/dev/openfeature/sdk/Client.java
index 2184cdc..7b41b9b 100644
--- a/src/main/java/dev/openfeature/sdk/Client.java
+++ b/src/main/java/dev/openfeature/sdk/Client.java
@@ -33,4 +33,10 @@ public interface Client extends Features, EventBus<Client> {
      * @return A list of {@link Hook}s.
      */
     List<Hook> getHooks();
+
+    /**
+     * Returns the current state of the associated provider.
+     * @return the provider state
+     */
+    ProviderState getProviderState();
 }
diff --git a/src/main/java/dev/openfeature/sdk/ErrorCode.java b/src/main/java/dev/openfeature/sdk/ErrorCode.java
index 6b387e2..00451bd 100644
--- a/src/main/java/dev/openfeature/sdk/ErrorCode.java
+++ b/src/main/java/dev/openfeature/sdk/ErrorCode.java
@@ -2,5 +2,6 @@ package dev.openfeature.sdk;
 
 @SuppressWarnings("checkstyle:MissingJavadocType")
 public enum ErrorCode {
-    PROVIDER_NOT_READY, FLAG_NOT_FOUND, PARSE_ERROR, TYPE_MISMATCH, TARGETING_KEY_MISSING, INVALID_CONTEXT, GENERAL
+    PROVIDER_NOT_READY, FLAG_NOT_FOUND, PARSE_ERROR, TYPE_MISMATCH, TARGETING_KEY_MISSING, INVALID_CONTEXT, GENERAL,
+    PROVIDER_FATAL
 }
diff --git a/src/main/java/dev/openfeature/sdk/EventProvider.java b/src/main/java/dev/openfeature/sdk/EventProvider.java
index 4e8c022..84893be 100644
--- a/src/main/java/dev/openfeature/sdk/EventProvider.java
+++ b/src/main/java/dev/openfeature/sdk/EventProvider.java
@@ -2,6 +2,7 @@ package dev.openfeature.sdk;
 
 import dev.openfeature.sdk.internal.TriConsumer;
 
+
 /**
  * Abstract EventProvider. Providers must extend this class to support events.
  * Emit events with {@link #emit(ProviderEvent, ProviderEventDetails)}. Please
@@ -15,22 +16,20 @@ import dev.openfeature.sdk.internal.TriConsumer;
  * @see FeatureProvider
  */
 public abstract class EventProvider implements FeatureProvider {
+    private EventProviderListener eventProviderListener;
 
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public abstract ProviderState getState();
+    void setEventProviderListener(EventProviderListener eventProviderListener) {
+        this.eventProviderListener = eventProviderListener;
+    }
 
     private TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> onEmit = null;
 
     /**
      * "Attach" this EventProvider to an SDK, which allows events to propagate from this provider.
-     * No-op if the same onEmit is already attached. 
+     * No-op if the same onEmit is already attached.
      *
      * @param onEmit the function to run when a provider emits events.
      * @throws IllegalStateException if attempted to bind a new emitter for already bound provider
-     *
      */
     void attach(TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> onEmit) {
         if (this.onEmit != null && this.onEmit != onEmit) {
@@ -50,11 +49,14 @@ public abstract class EventProvider implements FeatureProvider {
 
     /**
      * Emit the specified {@link ProviderEvent}.
-     * 
+     *
      * @param event   The event type
      * @param details The details of the event
      */
     public void emit(ProviderEvent event, ProviderEventDetails details) {
+        if (eventProviderListener != null) {
+            eventProviderListener.onEmit(event, details);
+        }
         if (this.onEmit != null) {
             this.onEmit.accept(this, event, details);
         }
@@ -63,7 +65,7 @@ public abstract class EventProvider implements FeatureProvider {
     /**
      * Emit a {@link ProviderEvent#PROVIDER_READY} event.
      * Shorthand for {@link #emit(ProviderEvent, ProviderEventDetails)}
-     * 
+     *
      * @param details The details of the event
      */
     public void emitProviderReady(ProviderEventDetails details) {
@@ -74,7 +76,7 @@ public abstract class EventProvider implements FeatureProvider {
      * Emit a
      * {@link ProviderEvent#PROVIDER_CONFIGURATION_CHANGED}
      * event. Shorthand for {@link #emit(ProviderEvent, ProviderEventDetails)}
-     * 
+     *
      * @param details The details of the event
      */
     public void emitProviderConfigurationChanged(ProviderEventDetails details) {
@@ -84,7 +86,7 @@ public abstract class EventProvider implements FeatureProvider {
     /**
      * Emit a {@link ProviderEvent#PROVIDER_STALE} event.
      * Shorthand for {@link #emit(ProviderEvent, ProviderEventDetails)}
-     * 
+     *
      * @param details The details of the event
      */
     public void emitProviderStale(ProviderEventDetails details) {
@@ -94,7 +96,7 @@ public abstract class EventProvider implements FeatureProvider {
     /**
      * Emit a {@link ProviderEvent#PROVIDER_ERROR} event.
      * Shorthand for {@link #emit(ProviderEvent, ProviderEventDetails)}
-     * 
+     *
      * @param details The details of the event
      */
     public void emitProviderError(ProviderEventDetails details) {
diff --git a/src/main/java/dev/openfeature/sdk/EventProviderListener.java b/src/main/java/dev/openfeature/sdk/EventProviderListener.java
new file mode 100644
index 0000000..c1f839a
--- /dev/null
+++ b/src/main/java/dev/openfeature/sdk/EventProviderListener.java
@@ -0,0 +1,6 @@
+package dev.openfeature.sdk;
+
+@FunctionalInterface
+interface EventProviderListener {
+    void onEmit(ProviderEvent event, ProviderEventDetails details);
+}
diff --git a/src/main/java/dev/openfeature/sdk/FeatureProvider.java b/src/main/java/dev/openfeature/sdk/FeatureProvider.java
index e1e06d0..f73b6cd 100644
--- a/src/main/java/dev/openfeature/sdk/FeatureProvider.java
+++ b/src/main/java/dev/openfeature/sdk/FeatureProvider.java
@@ -60,11 +60,13 @@ public interface FeatureProvider {
      * If the provider needs to be initialized, it should return {@link ProviderState#NOT_READY}.
      * If the provider is in an error state, it should return {@link ProviderState#ERROR}.
      * If the provider is functioning normally, it should return {@link ProviderState#READY}.
-     * 
+     *
      * <p><i>Providers which do not implement this method are assumed to be ready immediately.</i></p>
-     * 
+     *
      * @return ProviderState
+     * @deprecated The state is handled by the SDK internally. Query the state from the {@link Client} instead.
      */
+    @Deprecated
     default ProviderState getState() {
         return ProviderState.READY;
     }
diff --git a/src/main/java/dev/openfeature/sdk/FeatureProviderStateManager.java b/src/main/java/dev/openfeature/sdk/FeatureProviderStateManager.java
new file mode 100644
index 0000000..973d469
--- /dev/null
+++ b/src/main/java/dev/openfeature/sdk/FeatureProviderStateManager.java
@@ -0,0 +1,71 @@
+package dev.openfeature.sdk;
+
+import dev.openfeature.sdk.exceptions.OpenFeatureError;
+import lombok.Getter;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+class FeatureProviderStateManager implements EventProviderListener {
+    private final FeatureProvider delegate;
+    private final AtomicBoolean isInitialized = new AtomicBoolean();
+    @Getter
+    private ProviderState state = ProviderState.NOT_READY;
+
+    public FeatureProviderStateManager(FeatureProvider delegate) {
+        this.delegate = delegate;
+        if (delegate instanceof EventProvider) {
+            ((EventProvider) delegate).setEventProviderListener(this);
+        }
+    }
+
+    public void initialize(EvaluationContext evaluationContext) throws Exception {
+        if (isInitialized.getAndSet(true)) {
+            return;
+        }
+        try {
+            delegate.initialize(evaluationContext);
+            state = ProviderState.READY;
+        } catch (OpenFeatureError openFeatureError) {
+            if (ErrorCode.PROVIDER_FATAL.equals(openFeatureError.getErrorCode())) {
+                state = ProviderState.FATAL;
+            } else {
+                state = ProviderState.ERROR;
+            }
+            isInitialized.set(false);
+            throw openFeatureError;
+        } catch (Exception e) {
+            state = ProviderState.ERROR;
+            isInitialized.set(false);
+            throw e;
+        }
+    }
+
+    public void shutdown() {
+        delegate.shutdown();
+        state = ProviderState.NOT_READY;
+        isInitialized.set(false);
+    }
+
+    @Override
+    public void onEmit(ProviderEvent event, ProviderEventDetails details) {
+        if (ProviderEvent.PROVIDER_ERROR.equals(event)) {
+            if (details != null && details.getErrorCode() == ErrorCode.PROVIDER_FATAL) {
+                state = ProviderState.FATAL;
+            } else {
+                state = ProviderState.ERROR;
+            }
+        } else if (ProviderEvent.PROVIDER_STALE.equals(event)) {
+            state = ProviderState.STALE;
+        } else if (ProviderEvent.PROVIDER_READY.equals(event)) {
+            state = ProviderState.READY;
+        }
+    }
+
+    FeatureProvider getProvider() {
+        return delegate;
+    }
+
+    public boolean hasSameProvider(FeatureProvider featureProvider) {
+        return this.delegate.equals(featureProvider);
+    }
+}
diff --git a/src/main/java/dev/openfeature/sdk/NoOpProvider.java b/src/main/java/dev/openfeature/sdk/NoOpProvider.java
index ef8cf1f..2ad59c8 100644
--- a/src/main/java/dev/openfeature/sdk/NoOpProvider.java
+++ b/src/main/java/dev/openfeature/sdk/NoOpProvider.java
@@ -59,7 +59,7 @@ public class NoOpProvider implements FeatureProvider {
 
     @Override
     public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue,
-        EvaluationContext invocationContext) {
+                                                         EvaluationContext invocationContext) {
         return ProviderEvaluation.<Value>builder()
                 .value(defaultValue)
                 .variant(PASSED_IN_DEFAULT)
diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java
index f4d20ca..ad52856 100644
--- a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java
+++ b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java
@@ -5,11 +5,7 @@ import dev.openfeature.sdk.internal.AutoCloseableLock;
 import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock;
 import lombok.extern.slf4j.Slf4j;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
+import java.util.*;
 import java.util.function.Consumer;
 
 /**
@@ -69,7 +65,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
     }
 
     /**
-     * A factory function for creating new, OpenFeature clients.
+     * A factory function for creating new, OpenFeature client.
      * Clients can contain their own state (e.g. logger, hook, context).
      * Multiple clients can be used to segment feature flag configuration.
      * All un-named or unbound clients use the default provider.
@@ -81,12 +77,12 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
     }
 
     /**
-     * A factory function for creating new domainless OpenFeature clients.
+     * A factory function for creating new domainless OpenFeature client.
      * Clients can contain their own state (e.g. logger, hook, context).
      * Multiple clients can be used to segment feature flag configuration.
      * If there is already a provider bound to this domain, this provider will be used.
      * Otherwise, the default provider is used until a provider is assigned to that domain.
-     * 
+     *
      * @param domain an identifier which logically binds clients with providers
      * @return a new client instance
      */
@@ -95,20 +91,22 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
     }
 
     /**
-     * A factory function for creating new domainless OpenFeature clients.
+     * A factory function for creating new domainless OpenFeature client.
      * Clients can contain their own state (e.g. logger, hook, context).
      * Multiple clients can be used to segment feature flag configuration.
      * If there is already a provider bound to this domain, this provider will be used.
      * Otherwise, the default provider is used until a provider is assigned to that domain.
-     * 
-     * @param domain a identifier which logically binds clients with providers
+     *
+     * @param domain  a identifier which logically binds clients with providers
      * @param version a version identifier
      * @return a new client instance
      */
     public Client getClient(String domain, String version) {
-        return new OpenFeatureClient(this,
+        return new OpenFeatureClient(
+                this,
                 domain,
-                version);
+                version
+        );
     }
 
     /**
@@ -193,8 +191,8 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
     /**
      * Add a provider for a domain.
      *
-     * @param domain     The domain to bind the provider to.
-     * @param provider   The provider to set.
+     * @param domain   The domain to bind the provider to.
+     * @param provider The provider to set.
      */
     public void setProvider(String domain, FeatureProvider provider) {
         try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
@@ -226,8 +224,8 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
     /**
      * Add a provider for a domain and wait for initialization to finish.
      *
-     * @param domain     The domain to bind the provider to.
-     * @param provider   The provider to set.
+     * @param domain   The domain to bind the provider to.
+     * @param provider The provider to set.
      */
     public void setProviderAndWait(String domain, FeatureProvider provider) throws OpenFeatureError {
         try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
@@ -300,6 +298,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
 
     /**
      * Fetch the hooks associated to this client.
+     *
      * @return A list of {@link Hook}s.
      */
     public List<Hook> getHooks() {
@@ -394,7 +393,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
     void addHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler) {
         try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
             // if the provider is in the state associated with event, run immediately
-            if (Optional.ofNullable(this.providerRepository.getProvider(domain).getState())
+            if (Optional.ofNullable(this.providerRepository.getProviderState(domain))
                     .orElse(ProviderState.READY).matchesEvent(event)) {
                 eventSupport.runHandler(handler, EventDetails.builder().domain(domain).build());
             }
@@ -402,9 +401,13 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
         }
     }
 
+    FeatureProviderStateManager getFeatureProviderStateManager(String domain) {
+        return providerRepository.getFeatureProviderStateManager(domain);
+    }
+
     /**
      * Runs the handlers associated with a particular provider.
-     * 
+     *
      * @param provider the provider from where this event originated
      * @param event    the event type
      * @param details  the event details
diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
index 79982a2..2162f41 100644
--- a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
+++ b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
@@ -1,32 +1,26 @@
 package dev.openfeature.sdk;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Consumer;
-
-import dev.openfeature.sdk.exceptions.GeneralError;
-import dev.openfeature.sdk.exceptions.OpenFeatureError;
+import dev.openfeature.sdk.exceptions.*;
 import dev.openfeature.sdk.internal.AutoCloseableLock;
 import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock;
-import dev.openfeature.sdk.exceptions.ExceptionUtils;
 import dev.openfeature.sdk.internal.ObjectUtils;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 
+import java.util.*;
+import java.util.function.Consumer;
+
 /**
  * OpenFeature Client implementation.
  * You should not instantiate this or reference this class.
  * Use the dev.openfeature.sdk.Client interface instead.
- * @see Client
  *
+ * @see Client
  * @deprecated // TODO: eventually we will make this non-public. See issue #872
  */
 @Slf4j
-@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize",
-                    "PMD.UnusedLocalVariable", "unchecked", "rawtypes" })
+@SuppressWarnings({"PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize", "PMD.UnusedLocalVariable",
+    "unchecked", "rawtypes"})
 @Deprecated() // TODO: eventually we will make this non-public. See issue #872
 public class OpenFeatureClient implements Client {
 
@@ -48,11 +42,15 @@ public class OpenFeatureClient implements Client {
      * @param domain         An identifier which logically binds clients with providers (used by observability tools).
      * @param version        Version of the client (used by observability tools).
      * @deprecated Do not use this constructor. It's for internal use only.
-     *             Clients created using it will not run event handlers.
-     *             Use the OpenFeatureAPI's getClient factory method instead.
+     *     Clients created using it will not run event handlers.
+     *     Use the OpenFeatureAPI's getClient factory method instead.
      */
     @Deprecated() // TODO: eventually we will make this non-public. See issue #872
-    public OpenFeatureClient(OpenFeatureAPI openFeatureAPI, String domain, String version) {
+    public OpenFeatureClient(
+            OpenFeatureAPI openFeatureAPI,
+            String domain,
+            String version
+    ) {
         this.openfeatureApi = openFeatureAPI;
         this.domain = domain;
         this.version = version;
@@ -60,6 +58,11 @@ public class OpenFeatureClient implements Client {
         this.hookSupport = new HookSupport();
     }
 
+    @Override
+    public ProviderState getProviderState() {
+        return openfeatureApi.getFeatureProviderStateManager(domain).getState();
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -103,7 +106,7 @@ public class OpenFeatureClient implements Client {
     }
 
     private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key, T defaultValue,
-            EvaluationContext ctx, FlagEvaluationOptions options) {
+                                                      EvaluationContext ctx, FlagEvaluationOptions options) {
         FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(options,
                 () -> FlagEvaluationOptions.builder().build());
         Map<String, Object> hints = Collections.unmodifiableMap(flagOptions.getHookHints());
@@ -115,8 +118,16 @@ public class OpenFeatureClient implements Client {
         FeatureProvider provider;
 
         try {
-            // openfeatureApi.getProvider() must be called once to maintain a consistent reference
-            provider = openfeatureApi.getProvider(this.domain);
+            FeatureProviderStateManager stateManager = openfeatureApi.getFeatureProviderStateManager(this.domain);
+            // provider must be accessed once to maintain a consistent reference
+            provider = stateManager.getProvider();
+            ProviderState state = stateManager.getState();
+            if (ProviderState.NOT_READY.equals(state)) {
+                throw new ProviderNotReadyError("provider not yet initialized");
+            }
+            if (ProviderState.FATAL.equals(state)) {
+                throw new FatalError("provider is in an irrecoverable error state");
+            }
 
             mergedHooks = ObjectUtils.merge(provider.getProviderHooks(), flagOptions.getHooks(), clientHooks,
                     openfeatureApi.getHooks());
@@ -219,7 +230,7 @@ public class OpenFeatureClient implements Client {
 
     @Override
     public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationContext ctx,
-            FlagEvaluationOptions options) {
+                                   FlagEvaluationOptions options) {
         return getBooleanDetails(key, defaultValue, ctx, options).getValue();
     }
 
@@ -235,7 +246,7 @@ public class OpenFeatureClient implements Client {
 
     @Override
     public FlagEvaluationDetails<Boolean> getBooleanDetails(String key, Boolean defaultValue, EvaluationContext ctx,
-            FlagEvaluationOptions options) {
+                                                            FlagEvaluationOptions options) {
         return this.evaluateFlag(FlagValueType.BOOLEAN, key, defaultValue, ctx, options);
     }
 
@@ -251,7 +262,7 @@ public class OpenFeatureClient implements Client {
 
     @Override
     public String getStringValue(String key, String defaultValue, EvaluationContext ctx,
-            FlagEvaluationOptions options) {
+                                 FlagEvaluationOptions options) {
         return getStringDetails(key, defaultValue, ctx, options).getValue();
     }
 
@@ -267,7 +278,7 @@ public class OpenFeatureClient implements Client {
 
     @Override
     public FlagEvaluationDetails<String> getStringDetails(String key, String defaultValue, EvaluationContext ctx,
-            FlagEvaluationOptions options) {
+                                                          FlagEvaluationOptions options) {
         return this.evaluateFlag(FlagValueType.STRING, key, defaultValue, ctx, options);
     }
 
@@ -283,7 +294,7 @@ public class OpenFeatureClient implements Client {
 
     @Override
     public Integer getIntegerValue(String key, Integer defaultValue, EvaluationContext ctx,
-            FlagEvaluationOptions options) {
+                                   FlagEvaluationOptions options) {
         return getIntegerDetails(key, defaultValue, ctx, options).getValue();
     }
 
@@ -299,7 +310,7 @@ public class OpenFeatureClient implements Client {
 
     @Override
     public FlagEvaluationDetails<Integer> getIntegerDetails(String key, Integer defaultValue, EvaluationContext ctx,
-            FlagEvaluationOptions options) {
+                                                            FlagEvaluationOptions options) {
         return this.evaluateFlag(FlagValueType.INTEGER, key, defaultValue, ctx, options);
     }
 
@@ -315,7 +326,7 @@ public class OpenFeatureClient implements Client {
 
     @Override
     public Double getDoubleValue(String key, Double defaultValue, EvaluationContext ctx,
-            FlagEvaluationOptions options) {
+                                 FlagEvaluationOptions options) {
         return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options).getValue();
     }
 
@@ -331,7 +342,7 @@ public class OpenFeatureClient implements Client {
 
     @Override
     public FlagEvaluationDetails<Double> getDoubleDetails(String key, Double defaultValue, EvaluationContext ctx,
-            FlagEvaluationOptions options) {
+                                                          FlagEvaluationOptions options) {
         return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options);
     }
 
@@ -347,7 +358,7 @@ public class OpenFeatureClient implements Client {
 
     @Override
     public Value getObjectValue(String key, Value defaultValue, EvaluationContext ctx,
-            FlagEvaluationOptions options) {
+                                FlagEvaluationOptions options) {
         return getObjectDetails(key, defaultValue, ctx, options).getValue();
     }
 
@@ -358,13 +369,13 @@ public class OpenFeatureClient implements Client {
 
     @Override
     public FlagEvaluationDetails<Value> getObjectDetails(String key, Value defaultValue,
-            EvaluationContext ctx) {
+                                                         EvaluationContext ctx) {
         return getObjectDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build());
     }
 
     @Override
     public FlagEvaluationDetails<Value> getObjectDetails(String key, Value defaultValue, EvaluationContext ctx,
-            FlagEvaluationOptions options) {
+                                                         FlagEvaluationOptions options) {
         return this.evaluateFlag(FlagValueType.OBJECT, key, defaultValue, ctx, options);
     }
 
diff --git a/src/main/java/dev/openfeature/sdk/ProviderEventDetails.java b/src/main/java/dev/openfeature/sdk/ProviderEventDetails.java
index d28da9e..d927e42 100644
--- a/src/main/java/dev/openfeature/sdk/ProviderEventDetails.java
+++ b/src/main/java/dev/openfeature/sdk/ProviderEventDetails.java
@@ -13,4 +13,5 @@ public class ProviderEventDetails {
     private List<String> flagsChanged;
     private String message;
     private ImmutableMetadata eventMetadata;
+    private ErrorCode errorCode;
 }
diff --git a/src/main/java/dev/openfeature/sdk/ProviderRepository.java b/src/main/java/dev/openfeature/sdk/ProviderRepository.java
index 47b9ecf..d3a5c1a 100644
--- a/src/main/java/dev/openfeature/sdk/ProviderRepository.java
+++ b/src/main/java/dev/openfeature/sdk/ProviderRepository.java
@@ -1,5 +1,9 @@
 package dev.openfeature.sdk;
 
+import dev.openfeature.sdk.exceptions.GeneralError;
+import dev.openfeature.sdk.exceptions.OpenFeatureError;
+import lombok.extern.slf4j.Slf4j;
+
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -13,26 +17,41 @@ import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import dev.openfeature.sdk.exceptions.GeneralError;
-import dev.openfeature.sdk.exceptions.OpenFeatureError;
-import lombok.extern.slf4j.Slf4j;
-
 @Slf4j
 class ProviderRepository {
 
-    private final Map<String, FeatureProvider> providers = new ConcurrentHashMap<>();
-    private final AtomicReference<FeatureProvider> defaultProvider = new AtomicReference<>(new NoOpProvider());
+    private final Map<String, FeatureProviderStateManager> stateManagers = new ConcurrentHashMap<>();
+    private final AtomicReference<FeatureProviderStateManager> defaultStateManger = new AtomicReference<>(
+            new FeatureProviderStateManager(new NoOpProvider())
+    );
     private final ExecutorService taskExecutor = Executors.newCachedThreadPool(runnable -> {
         final Thread thread = new Thread(runnable);
         thread.setDaemon(true);
         return thread;
     });
+    private final Object registerStateManagerLock = new Object();
+
+    FeatureProviderStateManager getFeatureProviderStateManager() {
+        return defaultStateManger.get();
+    }
+
+    FeatureProviderStateManager getFeatureProviderStateManager(String domain) {
+        if (domain == null) {
+            return defaultStateManger.get();
+        }
+        FeatureProviderStateManager fromMap = this.stateManagers.get(domain);
+        if (fromMap == null) {
+            return this.defaultStateManger.get();
+        } else {
+            return fromMap;
+        }
+    }
 
     /**
      * Return the default provider.
      */
     public FeatureProvider getProvider() {
-        return defaultProvider.get();
+        return defaultStateManger.get().getProvider();
     }
 
     /**
@@ -42,17 +61,46 @@ class ProviderRepository {
      * @return A named {@link FeatureProvider}
      */
     public FeatureProvider getProvider(String domain) {
-        return Optional.ofNullable(domain).map(this.providers::get).orElse(this.defaultProvider.get());
+        return getFeatureProviderStateManager(domain).getProvider();
+    }
+
+    public ProviderState getProviderState() {
+        return getFeatureProviderStateManager().getState();
+    }
+
+    public ProviderState getProviderState(FeatureProvider featureProvider) {
+        if (featureProvider instanceof FeatureProviderStateManager) {
+            return ((FeatureProviderStateManager) featureProvider).getState();
+        }
+
+        FeatureProviderStateManager defaultProvider = this.defaultStateManger.get();
+        if (defaultProvider.hasSameProvider(featureProvider)) {
+            return defaultProvider.getState();
+        }
+
+        for (FeatureProviderStateManager wrapper : stateManagers.values()) {
+            if (wrapper.hasSameProvider(featureProvider)) {
+                return wrapper.getState();
+            }
+        }
+        return null;
+    }
+
+    public ProviderState getProviderState(String domain) {
+        return Optional.ofNullable(domain)
+                .map(this.stateManagers::get)
+                .orElse(this.defaultStateManger.get())
+                .getState();
     }
 
     public List<String> getDomainsForProvider(FeatureProvider provider) {
-        return providers.entrySet().stream()
-            .filter(entry -> entry.getValue().equals(provider))
-            .map(entry -> entry.getKey()).collect(Collectors.toList());
+        return stateManagers.entrySet().stream()
+                .filter(entry -> entry.getValue().hasSameProvider(provider))
+                .map(Map.Entry::getKey).collect(Collectors.toList());
     }
 
     public Set<String> getAllBoundDomains() {
-        return providers.keySet();
+        return stateManagers.keySet();
     }
 
     public boolean isDefaultProvider(FeatureProvider provider) {
@@ -63,11 +111,11 @@ class ProviderRepository {
      * Set the default provider.
      */
     public void setProvider(FeatureProvider provider,
-            Consumer<FeatureProvider> afterSet,
-            Consumer<FeatureProvider> afterInit,
-            Consumer<FeatureProvider> afterShutdown,
-            BiConsumer<FeatureProvider, OpenFeatureError> afterError,
-            boolean waitForInit) {
+                            Consumer<FeatureProvider> afterSet,
+                            Consumer<FeatureProvider> afterInit,
+                            Consumer<FeatureProvider> afterShutdown,
+                            BiConsumer<FeatureProvider, OpenFeatureError> afterError,
+                            boolean waitForInit) {
         if (provider == null) {
             throw new IllegalArgumentException("Provider cannot be null");
         }
@@ -83,12 +131,12 @@ class ProviderRepository {
      *                    Otherwise, initialization happens in the background.
      */
     public void setProvider(String domain,
-            FeatureProvider provider,
-            Consumer<FeatureProvider> afterSet,
-            Consumer<FeatureProvider> afterInit,
-            Consumer<FeatureProvider> afterShutdown,
-            BiConsumer<FeatureProvider, OpenFeatureError> afterError,
-            boolean waitForInit) {
+                            FeatureProvider provider,
+                            Consumer<FeatureProvider> afterSet,
+                            Consumer<FeatureProvider> afterInit,
+                            Consumer<FeatureProvider> afterShutdown,
+                            BiConsumer<FeatureProvider, OpenFeatureError> afterError,
+                            boolean waitForInit) {
         if (provider == null) {
             throw new IllegalArgumentException("Provider cannot be null");
         }
@@ -99,68 +147,105 @@ class ProviderRepository {
     }
 
     private void prepareAndInitializeProvider(String domain,
-              FeatureProvider newProvider,
-              Consumer<FeatureProvider> afterSet,
-              Consumer<FeatureProvider> afterInit,
-              Consumer<FeatureProvider> afterShutdown,
-              BiConsumer<FeatureProvider, OpenFeatureError> afterError,
-              boolean waitForInit) {
-
-        if (!isProviderRegistered(newProvider)) {
-            // only run afterSet if new provider is not already attached
-            afterSet.accept(newProvider);
-        }
+                                              FeatureProvider newProvider,
+                                              Consumer<FeatureProvider> afterSet,
+                                              Consumer<FeatureProvider> afterInit,
+                                              Consumer<FeatureProvider> afterShutdown,
+                                              BiConsumer<FeatureProvider, OpenFeatureError> afterError,
+                                              boolean waitForInit) {
+        final FeatureProviderStateManager newStateManager;
+        final FeatureProviderStateManager oldStateManager;
+
+        synchronized (registerStateManagerLock) {
+            FeatureProviderStateManager existing = getExistingStateManagerForProvider(newProvider);
+            if (existing == null) {
+                newStateManager = new FeatureProviderStateManager(newProvider);
+                // only run afterSet if new provider is not already attached
+                afterSet.accept(newProvider);
+            } else {
+                newStateManager = existing;
+            }
 
-        // provider is set immediately, on this thread
-        FeatureProvider oldProvider = domain != null
-                ? this.providers.put(domain, newProvider)
-                : this.defaultProvider.getAndSet(newProvider);
+            // provider is set immediately, on this thread
+            oldStateManager = domain != null
+                    ? this.stateManagers.put(domain, newStateManager)
+                    : this.defaultStateManger.getAndSet(newStateManager);
+        }
 
         if (waitForInit) {
-            initializeProvider(newProvider, afterInit, afterShutdown, afterError, oldProvider);
+            initializeProvider(newStateManager, afterInit, afterShutdown, afterError, oldStateManager);
         } else {
             taskExecutor.submit(() -> {
-                // initialization happens in a different thread if we're not waiting it
-                initializeProvider(newProvider, afterInit, afterShutdown, afterError, oldProvider);
+                // initialization happens in a different thread if we're not waiting for it
+                initializeProvider(newStateManager, afterInit, afterShutdown, afterError, oldStateManager);
             });
         }
     }
 
-    private void initializeProvider(FeatureProvider newProvider,
-            Consumer<FeatureProvider> afterInit,
-            Consumer<FeatureProvider> afterShutdown,
-            BiConsumer<FeatureProvider, OpenFeatureError> afterError,
-            FeatureProvider oldProvider) {
+    private FeatureProviderStateManager getExistingStateManagerForProvider(FeatureProvider provider) {
+        for (FeatureProviderStateManager stateManager : stateManagers.values()) {
+            if (stateManager.hasSameProvider(provider)) {
+                return stateManager;
+            }
+        }
+        FeatureProviderStateManager defaultFeatureProviderStateManager = defaultStateManger.get();
+        if (defaultFeatureProviderStateManager.hasSameProvider(provider)) {
+            return defaultFeatureProviderStateManager;
+        }
+        return null;
+    }
+
+    private void initializeProvider(FeatureProviderStateManager newManager,
+                                    Consumer<FeatureProvider> afterInit,
+                                    Consumer<FeatureProvider> afterShutdown,
+                                    BiConsumer<FeatureProvider, OpenFeatureError> afterError,
+                                    FeatureProviderStateManager oldManager) {
         try {
-            if (ProviderState.NOT_READY.equals(newProvider.getState())) {
-                newProvider.initialize(OpenFeatureAPI.getInstance().getEvaluationContext());
-                afterInit.accept(newProvider);
+            if (ProviderState.NOT_READY.equals(newManager.getState())) {
+                newManager.initialize(OpenFeatureAPI.getInstance().getEvaluationContext());
+                afterInit.accept(newManager.getProvider());
             }
-            shutDownOld(oldProvider, afterShutdown);
+            shutDownOld(oldManager, afterShutdown);
         } catch (OpenFeatureError e) {
-            log.error("Exception when initializing feature provider {}", newProvider.getClass().getName(), e);
-            afterError.accept(newProvider, e);
+            log.error(
+                    "Exception when initializing feature provider {}",
+                    newManager.getProvider().getClass().getName(),
+                    e
+            );
+            afterError.accept(newManager.getProvider(), e);
         } catch (Exception e) {
-            log.error("Exception when initializing feature provider {}", newProvider.getClass().getName(), e);
-            afterError.accept(newProvider, new GeneralError(e));
+            log.error(
+                    "Exception when initializing feature provider {}",
+                    newManager.getProvider().getClass().getName(),
+                    e
+            );
+            afterError.accept(newManager.getProvider(), new GeneralError(e));
         }
     }
 
-    private void shutDownOld(FeatureProvider oldProvider, Consumer<FeatureProvider> afterShutdown) {
-        if (!isProviderRegistered(oldProvider)) {
-            shutdownProvider(oldProvider);
-            afterShutdown.accept(oldProvider);
+    private void shutDownOld(FeatureProviderStateManager oldManager, Consumer<FeatureProvider> afterShutdown) {
+        if (oldManager != null && !isStateManagerRegistered(oldManager)) {
+            shutdownProvider(oldManager);
+            afterShutdown.accept(oldManager.getProvider());
         }
     }
 
     /**
-     * Helper to check if provider is already known (registered).
-     * @param provider provider to check for registration
+     * Helper to check if manager is already known (registered).
+     *
+     * @param manager manager to check for registration
      * @return boolean true if already registered, false otherwise
      */
-    private boolean isProviderRegistered(FeatureProvider provider) {
-        return provider != null
-                && (this.providers.containsValue(provider) || this.defaultProvider.get().equals(provider));
+    private boolean isStateManagerRegistered(FeatureProviderStateManager manager) {
+        return manager != null
+                && (this.stateManagers.containsValue(manager) || this.defaultStateManger.get().equals(manager));
+    }
+
+    private void shutdownProvider(FeatureProviderStateManager manager) {
+        if (manager == null) {
+            return;
+        }
+        shutdownProvider(manager.getProvider());
     }
 
     private void shutdownProvider(FeatureProvider provider) {
@@ -180,10 +265,10 @@ class ProviderRepository {
      */
     public void shutdown() {
         Stream
-                .concat(Stream.of(this.defaultProvider.get()), this.providers.values().stream())
+                .concat(Stream.of(this.defaultStateManger.get()), this.stateManagers.values().stream())
                 .distinct()
                 .forEach(this::shutdownProvider);
-        this.providers.clear();
+        this.stateManagers.clear();
         taskExecutor.shutdown();
     }
 }
diff --git a/src/main/java/dev/openfeature/sdk/ProviderState.java b/src/main/java/dev/openfeature/sdk/ProviderState.java
index a66d4e9..b924b4d 100644
--- a/src/main/java/dev/openfeature/sdk/ProviderState.java
+++ b/src/main/java/dev/openfeature/sdk/ProviderState.java
@@ -4,7 +4,7 @@ package dev.openfeature.sdk;
  * Indicates the state of the provider.
  */
 public enum ProviderState {
-    READY, NOT_READY, ERROR, STALE;
+    READY, NOT_READY, ERROR, STALE, FATAL;
 
     /**
      * Returns true if the passed ProviderEvent maps to this ProviderState.
diff --git a/src/main/java/dev/openfeature/sdk/exceptions/FatalError.java b/src/main/java/dev/openfeature/sdk/exceptions/FatalError.java
new file mode 100644
index 0000000..d50d1a4
--- /dev/null
+++ b/src/main/java/dev/openfeature/sdk/exceptions/FatalError.java
@@ -0,0 +1,13 @@
+package dev.openfeature.sdk.exceptions;
+
+import dev.openfeature.sdk.ErrorCode;
+import lombok.Getter;
+import lombok.experimental.StandardException;
+
+@SuppressWarnings("checkstyle:MissingJavadocType")
+@StandardException
+public class FatalError extends OpenFeatureError {
+    private static final long serialVersionUID = 1L;
+    @Getter
+    private final ErrorCode errorCode = ErrorCode.PROVIDER_FATAL;
+}
\ No newline at end of file
diff --git a/src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java b/src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java
index 5b6d287..2bea3e4 100644
--- a/src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java
+++ b/src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java
@@ -1,18 +1,7 @@
 package dev.openfeature.sdk.providers.memory;
 
-import dev.openfeature.sdk.EvaluationContext;
-import dev.openfeature.sdk.EventProvider;
-import dev.openfeature.sdk.Metadata;
-import dev.openfeature.sdk.ProviderEvaluation;
-import dev.openfeature.sdk.ProviderEventDetails;
-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.OpenFeatureError;
-import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
-import dev.openfeature.sdk.exceptions.TypeMismatchError;
+import dev.openfeature.sdk.*;
+import dev.openfeature.sdk.exceptions.*;
 import lombok.Getter;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
@@ -63,6 +52,7 @@ public class InMemoryProvider extends EventProvider {
      * Updates the provider flags configuration.
      * For existing flags, the new configurations replace the old one.
      * For new flags, they are added to the configuration.
+     *
      * @param newFlags the new flag configurations
      */
     public void updateFlags(Map<String, Flag<?>> newFlags) {
@@ -80,6 +70,7 @@ public class InMemoryProvider extends EventProvider {
      * Updates a single provider flag configuration.
      * For existing flag, the new configuration replaces the old one.
      * For new flag, they are added to the configuration.
+     *
      * @param newFlag the flag to update
      */
     public void updateFlag(String flagKey, Flag<?> newFlag) {
@@ -93,32 +84,32 @@ public class InMemoryProvider extends EventProvider {
 
     @Override
     public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue,
-            EvaluationContext evaluationContext) {
+                                                            EvaluationContext evaluationContext) {
         return getEvaluation(key, evaluationContext, Boolean.class);
     }
 
     @Override
     public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue,
-            EvaluationContext evaluationContext) {
+                                                          EvaluationContext evaluationContext) {
         return getEvaluation(key, evaluationContext, String.class);
     }
 
     @Override
     public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue,
-            EvaluationContext evaluationContext) {
+                                                            EvaluationContext evaluationContext) {
         return getEvaluation(key, evaluationContext, Integer.class);
     }
 
     @Override
     public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue,
-            EvaluationContext evaluationContext) {
+                                                          EvaluationContext evaluationContext) {
         return getEvaluation(key, evaluationContext, Double.class);
     }
 
     @SneakyThrows
     @Override
     public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue,
-            EvaluationContext evaluationContext) {
+                                                         EvaluationContext evaluationContext) {
         return getEvaluation(key, evaluationContext, Value.class);
     }
 
@@ -129,6 +120,9 @@ public class InMemoryProvider extends EventProvider {
             if (ProviderState.NOT_READY.equals(state)) {
                 throw new ProviderNotReadyError("provider not yet initialized");
             }
+            if (ProviderState.FATAL.equals(state)) {
+                throw new FatalError("provider in fatal error state");
+            }
             throw new GeneralError("unknown error");
         }
         Flag<?> flag = flags.get(key);
@@ -149,5 +143,4 @@ public class InMemoryProvider extends EventProvider {
             .reason(Reason.STATIC.toString())
             .build();
     }
-
 }


Test patch:
diff --git a/src/test/java/dev/openfeature/sdk/AlwaysBrokenProvider.java b/src/test/java/dev/openfeature/sdk/AlwaysBrokenProvider.java
index c109762..841d738 100644
--- a/src/test/java/dev/openfeature/sdk/AlwaysBrokenProvider.java
+++ b/src/test/java/dev/openfeature/sdk/AlwaysBrokenProvider.java
@@ -4,11 +4,11 @@ import dev.openfeature.sdk.exceptions.FlagNotFoundError;
 
 public class AlwaysBrokenProvider implements FeatureProvider {
 
+    private final String name = "always broken";
+
     @Override
     public Metadata getMetadata() {
-        return () -> {
-            throw new FlagNotFoundError(TestConstants.BROKEN_MESSAGE);
-        };
+        return () -> name;
     }
 
     @Override
diff --git a/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java b/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java
index b5e5bed..4502699 100644
--- a/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java
+++ b/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java
@@ -1,38 +1,38 @@
 package dev.openfeature.sdk;
 
+import dev.openfeature.sdk.fixtures.HookFixtures;
+import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
+import dev.openfeature.sdk.testutils.TestEventsProvider;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.Test;
+
+import java.util.*;
+
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
-import org.junit.jupiter.api.Test;
-
-import dev.openfeature.sdk.fixtures.HookFixtures;
-
 class DeveloperExperienceTest implements HookFixtures {
     transient String flagKey = "mykey";
 
-    @Test void simpleBooleanFlag() {
+    @Test
+    void simpleBooleanFlag() {
         OpenFeatureAPI api = OpenFeatureAPI.getInstance();
-        api.setProvider(new NoOpProvider());
+        api.setProviderAndWait(new TestEventsProvider());
         Client client = api.getClient();
         Boolean retval = client.getBooleanValue(flagKey, false);
         assertFalse(retval);
     }
 
-    @Test void clientHooks() {
+    @Test
+    void clientHooks() {
         Hook<Boolean> exampleHook = mockBooleanHook();
 
         OpenFeatureAPI api = OpenFeatureAPI.getInstance();
-        api.setProvider(new NoOpProvider());
+        api.setProviderAndWait(new TestEventsProvider());
         Client client = api.getClient();
         client.addHooks(exampleHook);
         Boolean retval = client.getBooleanValue(flagKey, false);
@@ -40,12 +40,13 @@ class DeveloperExperienceTest implements HookFixtures {
         assertFalse(retval);
     }
 
-    @Test void evalHooks() {
+    @Test
+    void evalHooks() {
         Hook<Boolean> clientHook = mockBooleanHook();
         Hook<Boolean> evalHook = mockBooleanHook();
 
         OpenFeatureAPI api = OpenFeatureAPI.getInstance();
-        api.setProvider(new NoOpProvider());
+        api.setProviderAndWait(new TestEventsProvider());
         Client client = api.getClient();
         client.addHooks(clientHook);
         Boolean retval = client.getBooleanValue(flagKey, false, null,
@@ -59,10 +60,11 @@ class DeveloperExperienceTest implements HookFixtures {
      * As an application author, you probably know special things about your users. You can communicate these to the
      * provider via {@link MutableContext}
      */
-    @Test void providingContext() {
+    @Test
+    void providingContext() {
 
         OpenFeatureAPI api = OpenFeatureAPI.getInstance();
-        api.setProvider(new NoOpProvider());
+        api.setProviderAndWait(new TestEventsProvider());
         Client client = api.getClient();
         Map<String, Value> attributes = new HashMap<>();
         List<Value> values = Arrays.asList(new Value(2), new Value(4));
@@ -76,7 +78,8 @@ class DeveloperExperienceTest implements HookFixtures {
         assertFalse(retval);
     }
 
-    @Test void brokenProvider() {
+    @Test
+    void brokenProvider() {
         OpenFeatureAPI api = OpenFeatureAPI.getInstance();
         FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenProvider());
         Client client = api.getClient();
@@ -93,13 +96,16 @@ class DeveloperExperienceTest implements HookFixtures {
         class MutatingHook implements Hook {
 
             @Override
+            @SneakyThrows
             // change the provider during a before hook - this should not impact the evaluation in progress
             public Optional before(HookContext ctx, Map hints) {
-                FeatureProviderTestUtils.setFeatureProvider(new NoOpProvider());
+
+                FeatureProviderTestUtils.setFeatureProvider(TestEventsProvider.newInitializedTestEventsProvider());
+
                 return Optional.empty();
             }
         }
-        
+
         final String defaultValue = "string-value";
         final OpenFeatureAPI api = OpenFeatureAPI.getInstance();
         final Client client = api.getClient();
@@ -111,9 +117,59 @@ class DeveloperExperienceTest implements HookFixtures {
         assertEquals(new StringBuilder(defaultValue).reverse().toString(), doSomethingValue);
 
         api.clearHooks();
-        
+
         // subsequent evaluations should now use new provider set by hook
         String noOpValue = client.getStringValue("val", defaultValue);
         assertEquals(noOpValue, defaultValue);
     }
+
+    @Test
+    void setProviderAndWaitShouldPutTheProviderInReadyState() {
+        String domain = "domain";
+        OpenFeatureAPI api = OpenFeatureAPI.getInstance();
+        api.setProviderAndWait(domain, new TestEventsProvider());
+        Client client = api.getClient(domain);
+        assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
+    }
+
+    @Specification(number = "5.3.5", text = "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.")
+    @Test
+    void shouldPutTheProviderInStateErrorAfterEmittingErrorEvent() {
+        String domain = "domain";
+        OpenFeatureAPI api = OpenFeatureAPI.getInstance();
+        TestEventsProvider provider = new TestEventsProvider();
+        api.setProviderAndWait(domain, provider);
+        Client client = api.getClient(domain);
+        assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
+        provider.emitProviderError(ProviderEventDetails.builder().build());
+        assertThat(client.getProviderState()).isEqualTo(ProviderState.ERROR);
+    }
+
+    @Specification(number = "5.3.5", text = "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.")
+    @Test
+    void shouldPutTheProviderInStateStaleAfterEmittingStaleEvent() {
+        String domain = "domain";
+        OpenFeatureAPI api = OpenFeatureAPI.getInstance();
+        TestEventsProvider provider = new TestEventsProvider();
+        api.setProviderAndWait(domain, provider);
+        Client client = api.getClient(domain);
+        assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
+        provider.emitProviderStale(ProviderEventDetails.builder().build());
+        assertThat(client.getProviderState()).isEqualTo(ProviderState.STALE);
+    }
+
+    @Specification(number = "5.3.5", text = "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.")
+    @Test
+    void shouldPutTheProviderInStateReadyAfterEmittingReadyEvent() {
+        String domain = "domain";
+        OpenFeatureAPI api = OpenFeatureAPI.getInstance();
+        TestEventsProvider provider = new TestEventsProvider();
+        api.setProviderAndWait(domain, provider);
+        Client client = api.getClient(domain);
+        assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
+        provider.emitProviderStale(ProviderEventDetails.builder().build());
+        assertThat(client.getProviderState()).isEqualTo(ProviderState.STALE);
+        provider.emitProviderReady(ProviderEventDetails.builder().build());
+        assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
+    }
 }
diff --git a/src/test/java/dev/openfeature/sdk/EventProviderTest.java b/src/test/java/dev/openfeature/sdk/EventProviderTest.java
index a5be858..acf2ce6 100644
--- a/src/test/java/dev/openfeature/sdk/EventProviderTest.java
+++ b/src/test/java/dev/openfeature/sdk/EventProviderTest.java
@@ -1,23 +1,34 @@
 package dev.openfeature.sdk;
 
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
+import dev.openfeature.sdk.exceptions.FatalError;
+import dev.openfeature.sdk.exceptions.GeneralError;
+import dev.openfeature.sdk.internal.TriConsumer;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
-import dev.openfeature.sdk.internal.TriConsumer;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
 
 class EventProviderTest {
 
+    private TestEventProvider eventProvider;
+
+    @BeforeEach
+    @SneakyThrows
+    void setup() {
+        eventProvider = new TestEventProvider();
+        eventProvider.initialize(null);
+    }
+
     @Test
     @DisplayName("should run attached onEmit with emitters")
     void emitsEventsWhenAttached() {
-        TestEventProvider eventProvider = new TestEventProvider();
         TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> onEmit = mockOnEmit();
         eventProvider.attach(onEmit);
 
@@ -37,8 +48,6 @@ class EventProviderTest {
     @Test
     @DisplayName("should do nothing with emitters if no onEmit attached")
     void doesNotEmitsEventsWhenNotAttached() {
-        TestEventProvider eventProvider = new TestEventProvider();
-
         // don't attach this emitter
         TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> onEmit = mockOnEmit();
 
@@ -56,7 +65,6 @@ class EventProviderTest {
     @Test
     @DisplayName("should throw if second different onEmit attached")
     void throwsWhenOnEmitDifferent() {
-        TestEventProvider eventProvider = new TestEventProvider();
         TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> onEmit1 = mockOnEmit();
         TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> onEmit2 = mockOnEmit();
         eventProvider.attach(onEmit1);
@@ -67,15 +75,13 @@ class EventProviderTest {
     @Test
     @DisplayName("should not throw if second same onEmit attached")
     void doesNotThrowWhenOnEmitSame() {
-        TestEventProvider eventProvider = new TestEventProvider();
         TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> onEmit1 = mockOnEmit();
         TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> onEmit2 = onEmit1;
         eventProvider.attach(onEmit1);
         eventProvider.attach(onEmit2); // should not throw, same instance. noop
     }
 
-
-    class TestEventProvider extends EventProvider {
+    static class TestEventProvider extends EventProvider {
 
         private static final String NAME = "TestEventProvider";
 
@@ -86,47 +92,42 @@ class EventProviderTest {
 
         @Override
         public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue,
-                EvaluationContext ctx) {
-            // TODO Auto-generated method stub
+                                                                EvaluationContext ctx) {
             throw new UnsupportedOperationException("Unimplemented method 'getBooleanEvaluation'");
         }
 
         @Override
         public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue,
-                EvaluationContext ctx) {
-            // TODO Auto-generated method stub
+                                                              EvaluationContext ctx) {
             throw new UnsupportedOperationException("Unimplemented method 'getStringEvaluation'");
         }
 
         @Override
         public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue,
-                EvaluationContext ctx) {
-            // TODO Auto-generated method stub
+                                                                EvaluationContext ctx) {
             throw new UnsupportedOperationException("Unimplemented method 'getIntegerEvaluation'");
         }
 
         @Override
         public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue,
-                EvaluationContext ctx) {
-            // TODO Auto-generated method stub
+                                                              EvaluationContext ctx) {
             throw new UnsupportedOperationException("Unimplemented method 'getDoubleEvaluation'");
         }
 
         @Override
         public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue,
-                EvaluationContext ctx) {
-            // TODO Auto-generated method stub
+                                                             EvaluationContext ctx) {
             throw new UnsupportedOperationException("Unimplemented method 'getObjectEvaluation'");
         }
 
         @Override
-        public ProviderState getState() {
-            return ProviderState.READY;
+        public void attach(TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> onEmit) {
+            super.attach(onEmit);
         }
     }
 
     @SuppressWarnings("unchecked")
     private TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> mockOnEmit() {
-        return (TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails>)mock(TriConsumer.class);
+        return (TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails>) mock(TriConsumer.class);
     }
 }
\ No newline at end of file
diff --git a/src/test/java/dev/openfeature/sdk/EventsTest.java b/src/test/java/dev/openfeature/sdk/EventsTest.java
index 6b70617..41bcf86 100644
--- a/src/test/java/dev/openfeature/sdk/EventsTest.java
+++ b/src/test/java/dev/openfeature/sdk/EventsTest.java
@@ -1,26 +1,22 @@
 package dev.openfeature.sdk;
 
-import static org.awaitility.Awaitility.await;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.after;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.Consumer;
-
+import dev.openfeature.sdk.testutils.TestEventsProvider;
+import io.cucumber.java.AfterAll;
+import lombok.SneakyThrows;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentMatcher;
 
-import dev.openfeature.sdk.testutils.TestEventsProvider;
-import io.cucumber.java.AfterAll;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.*;
 
 class EventsTest {
 
@@ -29,7 +25,7 @@ class EventsTest {
 
     @AfterAll
     public static void resetDefaultProvider() {
-        OpenFeatureAPI.getInstance().setProvider(new NoOpProvider());
+        OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider());
     }
 
     @Nested
@@ -53,7 +49,7 @@ class EventsTest {
 
                     TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
                     OpenFeatureAPI.getInstance().onProviderReady(handler);
-                    OpenFeatureAPI.getInstance().setProvider(name, provider);
+                    OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
                     verify(handler, timeout(TIMEOUT).atLeastOnce())
                             .accept(any());
                 }
@@ -90,7 +86,7 @@ class EventsTest {
                     final String name = "apiShouldPropagateEvents";
 
                     TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
-                    OpenFeatureAPI.getInstance().setProvider(name, provider);
+                    OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
                     OpenFeatureAPI.getInstance().onProviderConfigurationChanged(handler);
 
                     provider.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, EventDetails.builder().build());
@@ -113,7 +109,7 @@ class EventsTest {
                     final Consumer<EventDetails> handler4 = mockHandler();
 
                     TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
-                    OpenFeatureAPI.getInstance().setProvider(name, provider);
+                    OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
 
                     OpenFeatureAPI.getInstance().onProviderReady(handler1);
                     OpenFeatureAPI.getInstance().onProviderConfigurationChanged(handler2);
@@ -153,7 +149,7 @@ class EventsTest {
 
                     TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
                     // set provider before getting a client
-                    OpenFeatureAPI.getInstance().setProvider(provider);
+                    OpenFeatureAPI.getInstance().setProviderAndWait(provider);
                     Client client = OpenFeatureAPI.getInstance().getClient();
                     client.onProviderStale(handler);
 
@@ -170,7 +166,7 @@ class EventsTest {
 
                     TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
                     // set provider before getting a client
-                    OpenFeatureAPI.getInstance().setProvider(provider);
+                    OpenFeatureAPI.getInstance().setProviderAndWait(provider);
                     Client client = OpenFeatureAPI.getInstance().getClient(name);
                     client.onProviderStale(handler);
 
@@ -199,7 +195,7 @@ class EventsTest {
                 Client client = OpenFeatureAPI.getInstance().getClient(name);
                 client.onProviderReady(handler);
                 // set provider after getting a client
-                OpenFeatureAPI.getInstance().setProvider(name, provider);
+                OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
                 verify(handler, timeout(TIMEOUT).atLeastOnce())
                         .accept(argThat(details -> details.getDomain().equals(name)));
             }
@@ -213,7 +209,7 @@ class EventsTest {
 
                 TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
                 // set provider before getting a client
-                OpenFeatureAPI.getInstance().setProvider(name, provider);
+                OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
                 Client client = OpenFeatureAPI.getInstance().getClient(name);
                 client.onProviderReady(handler);
                 verify(handler, timeout(TIMEOUT).atLeastOnce())
@@ -272,7 +268,7 @@ class EventsTest {
 
                 TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
                 // set provider before getting a client
-                OpenFeatureAPI.getInstance().setProvider(name, provider);
+                OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
                 Client client = OpenFeatureAPI.getInstance().getClient(name);
                 client.onProviderConfigurationChanged(handler);
 
@@ -292,7 +288,7 @@ class EventsTest {
                 Client client = OpenFeatureAPI.getInstance().getClient(name);
                 client.onProviderConfigurationChanged(handler);
                 // set provider after getting a client
-                OpenFeatureAPI.getInstance().setProvider(name, provider);
+                OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
 
                 provider.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, EventDetails.builder().build());
                 verify(handler, timeout(TIMEOUT)).accept(argThat(details -> details.getDomain().equals(name)));
@@ -314,7 +310,7 @@ class EventsTest {
                 final Consumer<EventDetails> handler4 = mockHandler();
 
                 TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
-                OpenFeatureAPI.getInstance().setProvider(name, provider);
+                OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
                 Client client = OpenFeatureAPI.getInstance().getClient(name);
 
                 client.onProviderReady(handler1);
@@ -337,21 +333,21 @@ class EventsTest {
 
     @Test
     @DisplayName("shutdown provider should not run handlers")
-    void shouldNotRunHandlers()  {
+    void shouldNotRunHandlers() {
         final Consumer<EventDetails> handler1 = mockHandler();
         final Consumer<EventDetails> handler2 = mockHandler();
         final String name = "shouldNotRunHandlers";
 
         TestEventsProvider provider1 = new TestEventsProvider(INIT_DELAY);
         TestEventsProvider provider2 = new TestEventsProvider(INIT_DELAY);
-        OpenFeatureAPI.getInstance().setProvider(name, provider1);
+        OpenFeatureAPI.getInstance().setProviderAndWait(name, provider1);
         Client client = OpenFeatureAPI.getInstance().getClient(name);
 
         // attached handlers
         OpenFeatureAPI.getInstance().onProviderConfigurationChanged(handler1);
         client.onProviderConfigurationChanged(handler2);
 
-        OpenFeatureAPI.getInstance().setProvider(name, provider2);
+        OpenFeatureAPI.getInstance().setProviderAndWait(name, provider2);
 
         // wait for the new provider to be ready and make sure things are cleaned up.
         await().until(() -> provider1.isShutDown());
@@ -377,8 +373,8 @@ class EventsTest {
 
         TestEventsProvider provider1 = new TestEventsProvider(INIT_DELAY);
         TestEventsProvider provider2 = new TestEventsProvider(INIT_DELAY);
-        OpenFeatureAPI.getInstance().setProvider(name1, provider1);
-        OpenFeatureAPI.getInstance().setProvider(name2, provider2);
+        OpenFeatureAPI.getInstance().setProviderAndWait(name1, provider1);
+        OpenFeatureAPI.getInstance().setProviderAndWait(name2, provider2);
 
         Client client1 = OpenFeatureAPI.getInstance().getClient(name1);
         Client client2 = OpenFeatureAPI.getInstance().getClient(name2);
@@ -402,11 +398,11 @@ class EventsTest {
 
         TestEventsProvider namedProvider = new TestEventsProvider(INIT_DELAY);
         TestEventsProvider defaultProvider = new TestEventsProvider(INIT_DELAY);
-        OpenFeatureAPI.getInstance().setProvider(defaultProvider);
+        OpenFeatureAPI.getInstance().setProviderAndWait(defaultProvider);
 
         Client client = OpenFeatureAPI.getInstance().getClient(name);
         client.onProviderConfigurationChanged(handlerNotToRun);
-        OpenFeatureAPI.getInstance().setProvider(name, namedProvider);
+        OpenFeatureAPI.getInstance().setProviderAndWait(name, namedProvider);
 
         // await the new provider to make sure the old one is shut down
         await().until(() -> namedProvider.getState().equals(ProviderState.READY));
@@ -415,7 +411,7 @@ class EventsTest {
         defaultProvider.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, ProviderEventDetails.builder().build());
 
         verify(handlerNotToRun, after(TIMEOUT).never()).accept(any());
-        OpenFeatureAPI.getInstance().setProvider(new NoOpProvider());
+        OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider());
     }
 
     @Test
@@ -427,7 +423,7 @@ class EventsTest {
         final Consumer<EventDetails> handlerToRun = mockHandler();
 
         TestEventsProvider defaultProvider = new TestEventsProvider(INIT_DELAY);
-        OpenFeatureAPI.getInstance().setProvider(defaultProvider);
+        OpenFeatureAPI.getInstance().setProviderAndWait(defaultProvider);
 
         Client client = OpenFeatureAPI.getInstance().getClient(name);
         client.onProviderConfigurationChanged(handlerToRun);
@@ -439,7 +435,7 @@ class EventsTest {
         defaultProvider.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, ProviderEventDetails.builder().build());
 
         verify(handlerToRun, timeout(TIMEOUT)).accept(any());
-        OpenFeatureAPI.getInstance().setProvider(new NoOpProvider());
+        OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider());
     }
 
     @Test
@@ -453,7 +449,7 @@ class EventsTest {
         final Consumer<EventDetails> lastHandler = mockHandler();
 
         TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
-        OpenFeatureAPI.getInstance().setProvider(name, provider);
+        OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
 
         Client client1 = OpenFeatureAPI.getInstance().getClient(name);
 
@@ -477,7 +473,7 @@ class EventsTest {
         final String name = "shouldHaveAllProperties";
 
         TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
-        OpenFeatureAPI.getInstance().setProvider(name, provider);
+        OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
         Client client = OpenFeatureAPI.getInstance().getClient(name);
 
         // attached handlers
@@ -520,8 +516,8 @@ class EventsTest {
         final Consumer<EventDetails> handler = mockHandler();
 
         // provider which is already ready
-        TestEventsProvider provider = new TestEventsProvider(ProviderState.READY);
-        OpenFeatureAPI.getInstance().setProvider(name, provider);
+        TestEventsProvider provider = new TestEventsProvider();
+        OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
 
         // should run even thought handler was added after ready
         Client client = OpenFeatureAPI.getInstance().getClient(name);
@@ -535,13 +531,16 @@ class EventsTest {
     void matchingStaleEventsMustRunImmediately() {
         final String name = "matchingEventsMustRunImmediately";
         final Consumer<EventDetails> handler = mockHandler();
+        OpenFeatureAPI api = OpenFeatureAPI.getInstance();
 
         // provider which is already stale
-        TestEventsProvider provider = new TestEventsProvider(ProviderState.STALE);
-        OpenFeatureAPI.getInstance().setProvider(name, provider);
+        TestEventsProvider provider = TestEventsProvider.newInitializedTestEventsProvider();
+        Client client = api.getClient(name);
+        api.setProviderAndWait(name, provider);
+        provider.emitProviderStale(ProviderEventDetails.builder().build());
+        assertThat(client.getProviderState()).isEqualTo(ProviderState.STALE);
 
         // should run even thought handler was added after stale
-        Client client = OpenFeatureAPI.getInstance().getClient(name);
         client.onProviderStale(handler);
         verify(handler, timeout(TIMEOUT)).accept(any());
     }
@@ -552,13 +551,17 @@ class EventsTest {
     void matchingErrorEventsMustRunImmediately() {
         final String name = "matchingEventsMustRunImmediately";
         final Consumer<EventDetails> handler = mockHandler();
+        OpenFeatureAPI api = OpenFeatureAPI.getInstance();
 
         // provider which is already in error
-        TestEventsProvider provider = new TestEventsProvider(ProviderState.ERROR);
-        OpenFeatureAPI.getInstance().setProvider(name, provider);
+        TestEventsProvider provider = new TestEventsProvider();
+        Client client = api.getClient(name);
+        api.setProviderAndWait(name, provider);
+        provider.emitProviderError(ProviderEventDetails.builder().build());
+        assertThat(client.getProviderState()).isEqualTo(ProviderState.ERROR);
+
 
         // should run even thought handler was added after error
-        Client client = OpenFeatureAPI.getInstance().getClient(name);
         client.onProviderError(handler);
         verify(handler, timeout(TIMEOUT)).accept(any());
     }
@@ -573,7 +576,7 @@ class EventsTest {
         TestEventsProvider provider1 = new TestEventsProvider(INIT_DELAY);
         TestEventsProvider provider2 = new TestEventsProvider(INIT_DELAY);
 
-        OpenFeatureAPI.getInstance().setProvider(name, provider1);
+        OpenFeatureAPI.getInstance().setProviderAndWait(name, provider1);
         Client client = OpenFeatureAPI.getInstance().getClient(name);
         client.onProviderConfigurationChanged(handler);
 
@@ -583,8 +586,7 @@ class EventsTest {
         verify(handler, timeout(TIMEOUT).times(1)).accept(argThat(nameMatches));
 
         // wait for the new provider to be ready.
-        OpenFeatureAPI.getInstance().setProvider(name, provider2);
-        await().until(() -> provider2.getState().equals(ProviderState.READY));
+        OpenFeatureAPI.getInstance().setProviderAndWait(name, provider2);
 
         // verify that with the new provider under the same name, the handler is called
         // again.
@@ -594,16 +596,17 @@ class EventsTest {
 
     @Nested
     class HandlerRemoval {
-        @Specification(number="5.2.7", text="The API and client MUST provide a function allowing the removal of event handlers.")
+        @Specification(number = "5.2.7", text = "The API and client MUST provide a function allowing the removal of event handlers.")
         @Test
         @DisplayName("should not run removed events")
+        @SneakyThrows
         void removedEventsShouldNotRun() {
             final String name = "removedEventsShouldNotRun";
             final Consumer<EventDetails> handler1 = mockHandler();
             final Consumer<EventDetails> handler2 = mockHandler();
 
             TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
-            OpenFeatureAPI.getInstance().setProvider(name, provider);
+            OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
             Client client = OpenFeatureAPI.getInstance().getClient(name);
 
             // attached handlers
@@ -615,7 +618,7 @@ class EventsTest {
 
             // emit event
             provider.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, ProviderEventDetails.builder().build());
-
+            
             // both global and client handlers should not run.
             verify(handler1, after(TIMEOUT).never()).accept(any());
             verify(handler2, never()).accept(any());
diff --git a/src/test/java/dev/openfeature/sdk/FeatureProviderStateManagerTest.java b/src/test/java/dev/openfeature/sdk/FeatureProviderStateManagerTest.java
new file mode 100644
index 0000000..9d05524
--- /dev/null
+++ b/src/test/java/dev/openfeature/sdk/FeatureProviderStateManagerTest.java
@@ -0,0 +1,181 @@
+package dev.openfeature.sdk;
+
+import dev.openfeature.sdk.exceptions.FatalError;
+import dev.openfeature.sdk.exceptions.GeneralError;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import javax.annotation.Nullable;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class FeatureProviderStateManagerTest {
+
+    private FeatureProviderStateManager wrapper;
+    private TestDelegate testDelegate;
+
+    @BeforeEach
+    public void setUp() {
+        testDelegate = new TestDelegate();
+        wrapper = new FeatureProviderStateManager(testDelegate);
+    }
+
+    @SneakyThrows
+    @Test
+    void shouldOnlyCallInitOnce() {
+        wrapper.initialize(null);
+        wrapper.initialize(null);
+        assertThat(testDelegate.initCalled.get()).isOne();
+    }
+
+    @SneakyThrows
+    @Test
+    void shouldCallInitTwiceWhenShutDownInTheMeantime() {
+        wrapper.initialize(null);
+        wrapper.shutdown();
+        wrapper.initialize(null);
+        assertThat(testDelegate.initCalled.get()).isEqualTo(2);
+        assertThat(testDelegate.shutdownCalled.get()).isOne();
+    }
+
+    @Test
+    void shouldSetStateToNotReadyAfterConstruction() {
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY);
+    }
+
+    @SneakyThrows
+    @Test
+    @Specification(number = "1.7.3", text = "The client's provider status accessor MUST indicate READY if the initialize function of the associated provider terminates normally.")
+    void shouldSetStateToReadyAfterInit() {
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY);
+        wrapper.initialize(null);
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.READY);
+    }
+
+    @SneakyThrows
+    @Test
+    void shouldSetStateToNotReadyAfterShutdown() {
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY);
+        wrapper.initialize(null);
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.READY);
+        wrapper.shutdown();
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY);
+    }
+
+    @Specification(number = "1.7.4", text = "The client's provider status accessor MUST indicate ERROR if the initialize function of the associated provider terminates abnormally.")
+    @Test
+    void shouldSetStateToErrorAfterErrorOnInit() {
+        testDelegate.throwOnInit = new Exception();
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY);
+        assertThrows(Exception.class, () -> wrapper.initialize(null));
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.ERROR);
+    }
+
+    @Specification(number = "1.7.4", text = "The client's provider status accessor MUST indicate ERROR if the initialize function of the associated provider terminates abnormally.")
+    @Test
+    void shouldSetStateToErrorAfterOpenFeatureErrorOnInit() {
+        testDelegate.throwOnInit = new GeneralError();
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY);
+        assertThrows(GeneralError.class, () -> wrapper.initialize(null));
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.ERROR);
+    }
+
+    @Specification(number = "1.7.5", text = "The client's provider status accessor MUST indicate FATAL if the initialize function of the associated provider terminates abnormally and indicates error code PROVIDER_FATAL.")
+    @Test
+    void shouldSetStateToErrorAfterFatalErrorOnInit() {
+        testDelegate.throwOnInit = new FatalError();
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY);
+        assertThrows(FatalError.class, () -> wrapper.initialize(null));
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.FATAL);
+    }
+
+    @Specification(number = "5.3.5", text = "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.")
+    @Test
+    void shouldSetTheStateToReadyWhenAReadyEventIsEmitted() {
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY);
+        wrapper.onEmit(ProviderEvent.PROVIDER_READY, null);
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.READY);
+    }
+
+    @Specification(number = "5.3.5", text = "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.")
+    @Test
+    void shouldSetTheStateToStaleWhenAStaleEventIsEmitted() {
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY);
+        wrapper.onEmit(ProviderEvent.PROVIDER_STALE, null);
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.STALE);
+    }
+
+    @Specification(number = "5.3.5", text = "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.")
+    @Test
+    void shouldSetTheStateToErrorWhenAnErrorEventIsEmitted() {
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY);
+        wrapper.onEmit(
+                ProviderEvent.PROVIDER_ERROR,
+                ProviderEventDetails.builder().errorCode(ErrorCode.GENERAL).build()
+        );
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.ERROR);
+    }
+
+    @Specification(number = "5.3.5", text = "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.")
+    @Test
+    void shouldSetTheStateToFatalWhenAFatalErrorEventIsEmitted() {
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY);
+        wrapper.onEmit(
+                ProviderEvent.PROVIDER_ERROR,
+                ProviderEventDetails.builder().errorCode(ErrorCode.PROVIDER_FATAL).build()
+        );
+        assertThat(wrapper.getState()).isEqualTo(ProviderState.FATAL);
+    }
+
+    static class TestDelegate extends EventProvider {
+        private final AtomicInteger initCalled = new AtomicInteger();
+        private final AtomicInteger shutdownCalled = new AtomicInteger();
+        private @Nullable Exception throwOnInit;
+
+        @Override
+        public Metadata getMetadata() {
+            return null;
+        }
+
+        @Override
+        public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
+            return null;
+        }
+
+        @Override
+        public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
+            return null;
+        }
+
+        @Override
+        public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
+            return null;
+        }
+
+        @Override
+        public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
+            return null;
+        }
+
+        @Override
+        public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) {
+            return null;
+        }
+
+        @Override
+        public void initialize(EvaluationContext evaluationContext) throws Exception {
+            initCalled.incrementAndGet();
+            if (throwOnInit != null) {
+                throw throwOnInit;
+            }
+        }
+
+        @Override
+        public void shutdown() {
+            shutdownCalled.incrementAndGet();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
index c4a6fd6..a2316a5 100644
--- a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
+++ b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
@@ -1,28 +1,11 @@
 package dev.openfeature.sdk;
 
-import static dev.openfeature.sdk.DoSomethingProvider.DEFAULT_METADATA;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-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 dev.openfeature.sdk.exceptions.GeneralError;
+import dev.openfeature.sdk.fixtures.HookFixtures;
+import dev.openfeature.sdk.providers.memory.InMemoryProvider;
+import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
+import dev.openfeature.sdk.testutils.TestEventsProvider;
+import lombok.SneakyThrows;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -30,12 +13,17 @@ import org.mockito.Mockito;
 import org.simplify4u.slf4jmock.LoggerMock;
 import org.slf4j.Logger;
 
-import dev.openfeature.sdk.exceptions.GeneralError;
-import dev.openfeature.sdk.fixtures.HookFixtures;
-import dev.openfeature.sdk.providers.memory.InMemoryProvider;
-import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
-import dev.openfeature.sdk.testutils.TestEventsProvider;
-import lombok.SneakyThrows;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static dev.openfeature.sdk.DoSomethingProvider.DEFAULT_METADATA;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.*;
 
 class FlagEvaluationSpecTest implements HookFixtures {
 
@@ -47,6 +35,14 @@ class FlagEvaluationSpecTest implements HookFixtures {
         return api.getClient();
     }
 
+    @SneakyThrows
+    private Client _initializedClient() {
+        TestEventsProvider provider = new TestEventsProvider();
+        provider.initialize(null);
+        FeatureProviderTestUtils.setFeatureProvider(provider);
+        return api.getClient();
+    }
+
     @BeforeEach
     void getApiInstance() {
         api = OpenFeatureAPI.getInstance();
@@ -82,12 +78,14 @@ class FlagEvaluationSpecTest implements HookFixtures {
     @Test void providerAndWait() {
         FeatureProvider provider = new TestEventsProvider(500);
         OpenFeatureAPI.getInstance().setProviderAndWait(provider);
-        assertThat(api.getProvider().getState()).isEqualTo(ProviderState.READY);
+        Client client = api.getClient();
+        assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
 
         provider = new TestEventsProvider(500);
         String providerName = "providerAndWait";
         OpenFeatureAPI.getInstance().setProviderAndWait(providerName, provider);
-        assertThat(api.getProvider(providerName).getState()).isEqualTo(ProviderState.READY);
+        Client client2 = api.getClient(providerName);
+        assertThat(client2.getProviderState()).isEqualTo(ProviderState.READY);
     }
 
     @SneakyThrows
@@ -103,15 +101,9 @@ class FlagEvaluationSpecTest implements HookFixtures {
 
     @Specification(number="2.4.5", text="The provider SHOULD indicate an error if flag resolution is attempted before the provider is ready.")
     @Test void shouldReturnNotReadyIfNotInitialized() {
-        FeatureProvider provider = new InMemoryProvider(new HashMap<>()) {
-            @Override
-            public void initialize(EvaluationContext evaluationContext) throws Exception {
-                Awaitility.await().wait(3000);
-            }
-        };
+        FeatureProvider provider = new TestEventsProvider(100);
         String providerName = "shouldReturnNotReadyIfNotInitialized";
         OpenFeatureAPI.getInstance().setProvider(providerName, provider);
-        assertThat(api.getProvider(providerName).getState()).isEqualTo(ProviderState.NOT_READY);
         Client client = OpenFeatureAPI.getInstance().getClient(providerName);
         FlagEvaluationDetails<Boolean> details = client.getBooleanDetails("return_error_when_not_initialized", false);
         assertEquals(ErrorCode.PROVIDER_NOT_READY, details.getErrorCode());
@@ -242,8 +234,9 @@ class FlagEvaluationSpecTest implements HookFixtures {
     }
 
     @Specification(number="1.5.1", text="The evaluation options structure's hooks field denotes an ordered collection of hooks that the client MUST execute for the respective flag evaluation, in addition to those already configured.")
-    @Test void hooks() {
-        Client c = _client();
+    @SneakyThrows
+    @Test void hooks()  {
+        Client c = _initializedClient();
         Hook<Boolean> clientHook = mockBooleanHook();
         Hook<Boolean> invocationHook = mockBooleanHook();
         c.addHooks(clientHook);
diff --git a/src/test/java/dev/openfeature/sdk/HookSpecTest.java b/src/test/java/dev/openfeature/sdk/HookSpecTest.java
index 2554457..d0d759f 100644
--- a/src/test/java/dev/openfeature/sdk/HookSpecTest.java
+++ b/src/test/java/dev/openfeature/sdk/HookSpecTest.java
@@ -3,34 +3,20 @@ package dev.openfeature.sdk;
 import dev.openfeature.sdk.exceptions.FlagNotFoundError;
 import dev.openfeature.sdk.fixtures.HookFixtures;
 import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
+import dev.openfeature.sdk.testutils.TestEventsProvider;
 import lombok.SneakyThrows;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
 
 import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.Assertions.fail;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 class HookSpecTest implements HookFixtures {
     @AfterEach
@@ -39,8 +25,9 @@ class HookSpecTest implements HookFixtures {
         OpenFeatureAPI.getInstance().clearHooks();
     }
 
-    @Specification(number="4.1.3", text="The flag key, flag type, and default value properties MUST be immutable. If the language does not support immutability, the hook MUST NOT modify these properties.")
-    @Test void immutableValues() {
+    @Specification(number = "4.1.3", text = "The flag key, flag type, and default value properties MUST be immutable. If the language does not support immutability, the hook MUST NOT modify these properties.")
+    @Test
+    void immutableValues() {
         try {
             HookContext.class.getMethod("setFlagKey");
             fail("Shouldn't be able to find this method");
@@ -63,8 +50,9 @@ class HookSpecTest implements HookFixtures {
         }
     }
 
-    @Specification(number="4.1.1", text="Hook context MUST provide: the flag key, flag value type, evaluation context, and the default value.")
-    @Test void nullish_properties_on_hookcontext() {
+    @Specification(number = "4.1.1", text = "Hook context MUST provide: the flag key, flag value type, evaluation context, and the default value.")
+    @Test
+    void nullish_properties_on_hookcontext() {
         // missing ctx
         try {
             HookContext.<Integer>builder()
@@ -127,8 +115,9 @@ class HookSpecTest implements HookFixtures {
 
     }
 
-    @Specification(number="4.1.2", text="The hook context SHOULD provide: access to the client metadata and the provider metadata fields.")
-    @Test void optional_properties() {
+    @Specification(number = "4.1.2", text = "The hook context SHOULD provide: access to the client metadata and the provider metadata fields.")
+    @Test
+    void optional_properties() {
         // don't specify
         HookContext.<Integer>builder()
                 .flagKey("key")
@@ -156,10 +145,11 @@ class HookSpecTest implements HookFixtures {
                 .build();
     }
 
-    @Specification(number="4.3.2.1", text="The before stage MUST run before flag resolution occurs. It accepts a hook context (required) and hook hints (optional) as parameters and returns either an evaluation context or nothing.")
-    @Test void before_runs_ahead_of_evaluation() {
+    @Specification(number = "4.3.2.1", text = "The before stage MUST run before flag resolution occurs. It accepts a hook context (required) and hook hints (optional) as parameters and returns either an evaluation context or nothing.")
+    @Test
+    void before_runs_ahead_of_evaluation() {
         OpenFeatureAPI api = OpenFeatureAPI.getInstance();
-        api.setProvider(new AlwaysBrokenProvider());
+        api.setProviderAndWait(new AlwaysBrokenProvider());
         Client client = api.getClient();
         Hook<Boolean> evalHook = mockBooleanHook();
 
@@ -169,13 +159,15 @@ class HookSpecTest implements HookFixtures {
         verify(evalHook, times(1)).before(any(), any());
     }
 
-    @Test void feo_has_hook_list() {
+    @Test
+    void feo_has_hook_list() {
         FlagEvaluationOptions feo = FlagEvaluationOptions.builder()
                 .build();
         assertNotNull(feo.getHooks());
     }
 
-    @Test void error_hook_run_during_non_finally_stage() {
+    @Test
+    void error_hook_run_during_non_finally_stage() {
         final boolean[] error_called = {false};
         Hook h = mockBooleanHook();
         doThrow(RuntimeException.class).when(h).finallyAfter(any(), any());
@@ -184,7 +176,8 @@ class HookSpecTest implements HookFixtures {
     }
 
 
-    @Test void error_hook_must_run_if_resolution_details_returns_an_error_code() {
+    @Test
+    void error_hook_must_run_if_resolution_details_returns_an_error_code() {
 
         String errorMessage = "not found...";
 
@@ -209,7 +202,7 @@ class HookSpecTest implements HookFixtures {
         verify(hook, times(1)).before(any(), any());
         verify(hook, times(1)).error(any(), captor.capture(), any());
         verify(hook, times(1)).finallyAfter(any(), any());
-        verify(hook, never()).after(any(),any(), any());
+        verify(hook, never()).after(any(), any(), any());
 
         Exception exception = captor.getValue();
         assertEquals(errorMessage, exception.getMessage());
@@ -217,15 +210,16 @@ class HookSpecTest implements HookFixtures {
     }
 
 
-    @Specification(number="4.3.6", text="The after stage MUST run after flag resolution occurs. It accepts a hook context (required), flag evaluation details (required) and hook hints (optional). It has no return value.")
-    @Specification(number="4.3.7", text="The error hook MUST run when errors are encountered in the before stage, the after stage or during flag resolution. It accepts hook context (required), exception representing what went wrong (required), and hook hints (optional). It has no return value.")
-    @Specification(number="4.3.8", text="The finally hook MUST run after the before, after, and error stages. It accepts a hook context (required) and hook hints (optional). There is no return value.")
-    @Specification(number="4.4.1", text="The API, Client, Provider, and invocation MUST have a method for registering hooks.")
-    @Specification(number="4.4.2", text="Hooks MUST be evaluated in the following order:  - before: API, Client, Invocation, Provider - after: Provider, Invocation, Client, API - error (if applicable): Provider, Invocation, Client, API - finally: Provider, Invocation, Client, API")
-    @Test void hook_eval_order() {
+    @Specification(number = "4.3.6", text = "The after stage MUST run after flag resolution occurs. It accepts a hook context (required), flag evaluation details (required) and hook hints (optional). It has no return value.")
+    @Specification(number = "4.3.7", text = "The error hook MUST run when errors are encountered in the before stage, the after stage or during flag resolution. It accepts hook context (required), exception representing what went wrong (required), and hook hints (optional). It has no return value.")
+    @Specification(number = "4.3.8", text = "The finally hook MUST run after the before, after, and error stages. It accepts a hook context (required) and hook hints (optional). There is no return value.")
+    @Specification(number = "4.4.1", text = "The API, Client, Provider, and invocation MUST have a method for registering hooks.")
+    @Specification(number = "4.4.2", text = "Hooks MUST be evaluated in the following order:  - before: API, Client, Invocation, Provider - after: Provider, Invocation, Client, API - error (if applicable): Provider, Invocation, Client, API - finally: Provider, Invocation, Client, API")
+    @Test
+    void hook_eval_order() {
         List<String> evalOrder = new ArrayList<>();
         OpenFeatureAPI api = OpenFeatureAPI.getInstance();
-        api.setProvider(new NoOpProvider() {
+        api.setProvider("evalOrder", new TestEventsProvider() {
             public List<Hook> getProviderHooks() {
                 return Collections.singletonList(new BooleanHook() {
 
@@ -277,7 +271,7 @@ class HookSpecTest implements HookFixtures {
             }
         });
 
-        Client c = api.getClient();
+        Client c = api.getClient("evalOrder");
         c.addHooks(new BooleanHook() {
             @Override
             public Optional<EvaluationContext> before(HookContext<Boolean> ctx, Map<String, Object> hints) {
@@ -302,64 +296,67 @@ class HookSpecTest implements HookFixtures {
         });
 
         c.getBooleanValue("key", false, null, FlagEvaluationOptions
-            .builder()
-            .hook(new BooleanHook() {
-                @Override
-                public Optional<EvaluationContext> before(HookContext<Boolean> ctx, Map<String, Object> hints) {
-                    evalOrder.add("invocation before");
-                    return null;
-                }
-
-                @Override
-                public void after(HookContext<Boolean> ctx, FlagEvaluationDetails<Boolean> details, Map<String, Object> hints) {
-                    evalOrder.add("invocation after");
-                }
-
-                @Override
-                public void error(HookContext<Boolean> ctx, Exception error, Map<String, Object> hints) {
-                    evalOrder.add("invocation error");
-                }
-
-                @Override
-                public void finallyAfter(HookContext<Boolean> ctx, Map<String, Object> hints) {
-                    evalOrder.add("invocation finally");
-                }
-            })
-            .build());
+                .builder()
+                .hook(new BooleanHook() {
+                    @Override
+                    public Optional<EvaluationContext> before(HookContext<Boolean> ctx, Map<String, Object> hints) {
+                        evalOrder.add("invocation before");
+                        return null;
+                    }
+
+                    @Override
+                    public void after(HookContext<Boolean> ctx, FlagEvaluationDetails<Boolean> details, Map<String, Object> hints) {
+                        evalOrder.add("invocation after");
+                    }
+
+                    @Override
+                    public void error(HookContext<Boolean> ctx, Exception error, Map<String, Object> hints) {
+                        evalOrder.add("invocation error");
+                    }
+
+                    @Override
+                    public void finallyAfter(HookContext<Boolean> ctx, Map<String, Object> hints) {
+                        evalOrder.add("invocation finally");
+                    }
+                })
+                .build());
 
         List<String> expectedOrder = Arrays.asList(
-            "api before", "client before", "invocation before", "provider before",
-            "provider after", "invocation after", "client after", "api after",
-            "provider error", "invocation error", "client error", "api error",
-            "provider finally", "invocation finally", "client finally", "api finally");
+                "api before", "client before", "invocation before", "provider before",
+                "provider after", "invocation after", "client after", "api after",
+                "provider error", "invocation error", "client error", "api error",
+                "provider finally", "invocation finally", "client finally", "api finally");
         assertEquals(expectedOrder, evalOrder);
     }
 
-    @Specification(number="4.4.6", text="If an error occurs during the evaluation of before or after hooks, any remaining hooks in the before or after stages MUST NOT be invoked.")
-    @Test void error_stops_before() {
+    @Specification(number = "4.4.6", text = "If an error occurs during the evaluation of before or after hooks, any remaining hooks in the before or after stages MUST NOT be invoked.")
+    @Test
+    void error_stops_before() {
         Hook<Boolean> h = mockBooleanHook();
         doThrow(RuntimeException.class).when(h).before(any(), any());
         Hook<Boolean> h2 = mockBooleanHook();
 
         OpenFeatureAPI api = OpenFeatureAPI.getInstance();
-        api.setProvider(new AlwaysBrokenProvider());
+        api.setProviderAndWait(new AlwaysBrokenProvider());
         Client c = api.getClient();
 
         c.getBooleanDetails("key", false, null, FlagEvaluationOptions.builder()
                 .hook(h2)
                 .hook(h)
                 .build());
-        verify(h, times(1)).before(any(), any());
-        verify(h2, times(0)).before(any(), any());
+            verify(h, times(1)).before(any(), any());
+            verify(h2, times(0)).before(any(), any());
     }
 
-    @Specification(number="4.4.6", text="If an error occurs during the evaluation of before or after hooks, any remaining hooks in the before or after stages MUST NOT be invoked.")
-    @Test void error_stops_after() {
+    @Specification(number = "4.4.6", text = "If an error occurs during the evaluation of before or after hooks, any remaining hooks in the before or after stages MUST NOT be invoked.")
+    @SneakyThrows
+    @Test
+    void error_stops_after() {
         Hook<Boolean> h = mockBooleanHook();
         doThrow(RuntimeException.class).when(h).after(any(), any(), any());
         Hook<Boolean> h2 = mockBooleanHook();
 
-        Client c = getClient(null);
+        Client c = getClient(TestEventsProvider.newInitializedTestEventsProvider());
 
         c.getBooleanDetails("key", false, null, FlagEvaluationOptions.builder()
                 .hook(h)
@@ -369,11 +366,13 @@ class HookSpecTest implements HookFixtures {
         verify(h2, times(0)).after(any(), any(), any());
     }
 
-    @Specification(number="4.2.1", text="hook hints MUST be a structure supports definition of arbitrary properties, with keys of type string, and values of type boolean | string | number | datetime | structure..")
-    @Specification(number="4.5.2", text="hook hints MUST be passed to each hook.")
-    @Specification(number="4.2.2.1", text="Condition: Hook hints MUST be immutable.")
-    @Specification(number="4.5.3", text="The hook MUST NOT alter the hook hints structure.")
-    @Test void hook_hints() {
+    @Specification(number = "4.2.1", text = "hook hints MUST be a structure supports definition of arbitrary properties, with keys of type string, and values of type boolean | string | number | datetime | structure..")
+    @Specification(number = "4.5.2", text = "hook hints MUST be passed to each hook.")
+    @Specification(number = "4.2.2.1", text = "Condition: Hook hints MUST be immutable.")
+    @Specification(number = "4.5.3", text = "The hook MUST NOT alter the hook hints structure.")
+    @SneakyThrows
+    @Test
+    void hook_hints() {
         String hintKey = "My hint key";
         Client client = getClient(null);
         Hook<Boolean> mutatingHook = new BooleanHook() {
@@ -409,14 +408,16 @@ class HookSpecTest implements HookFixtures {
                 .build());
     }
 
-    @Specification(number="4.5.1", text="Flag evaluation options MAY contain hook hints, a map of data to be provided to hook invocations.")
-    @Test void missing_hook_hints() {
+    @Specification(number = "4.5.1", text = "Flag evaluation options MAY contain hook hints, a map of data to be provided to hook invocations.")
+    @Test
+    void missing_hook_hints() {
         FlagEvaluationOptions feo = FlagEvaluationOptions.builder().build();
         assertNotNull(feo.getHookHints());
         assertTrue(feo.getHookHints().isEmpty());
     }
 
-    @Test void flag_eval_hook_order() {
+    @Test
+    void flag_eval_hook_order() {
         Hook hook = mockBooleanHook();
         FeatureProvider provider = mock(FeatureProvider.class);
         when(provider.getBooleanEvaluation(any(), any(), any()))
@@ -437,12 +438,13 @@ class HookSpecTest implements HookFixtures {
         order.verify(hook).finallyAfter(any(), any());
     }
 
-    @Specification(number="4.4.5", text="If an error occurs in the before or after hooks, the error hooks MUST be invoked.")
-    @Specification(number="4.4.7", text="If an error occurs in the before hooks, the default value MUST be returned.")
-    @Test void error_hooks__before() {
+    @Specification(number = "4.4.5", text = "If an error occurs in the before or after hooks, the error hooks MUST be invoked.")
+    @Specification(number = "4.4.7", text = "If an error occurs in the before hooks, the default value MUST be returned.")
+    @Test
+    void error_hooks__before() {
         Hook hook = mockBooleanHook();
         doThrow(RuntimeException.class).when(hook).before(any(), any());
-        Client client = getClient(null);
+        Client client = getClient(TestEventsProvider.newInitializedTestEventsProvider());
         Boolean value = client.getBooleanValue("key", false, new ImmutableContext(),
                 FlagEvaluationOptions.builder().hook(hook).build());
         verify(hook, times(1)).before(any(), any());
@@ -450,18 +452,20 @@ class HookSpecTest implements HookFixtures {
         assertEquals(false, value, "Falls through to the default.");
     }
 
-    @Specification(number="4.4.5", text="If an error occurs in the before or after hooks, the error hooks MUST be invoked.")
-    @Test void error_hooks__after() {
+    @Specification(number = "4.4.5", text = "If an error occurs in the before or after hooks, the error hooks MUST be invoked.")
+    @Test
+    void error_hooks__after() {
         Hook hook = mockBooleanHook();
         doThrow(RuntimeException.class).when(hook).after(any(), any(), any());
-        Client client = getClient(null);
+        Client client = getClient(TestEventsProvider.newInitializedTestEventsProvider());
         client.getBooleanValue("key", false, new ImmutableContext(),
                 FlagEvaluationOptions.builder().hook(hook).build());
         verify(hook, times(1)).after(any(), any(), any());
         verify(hook, times(1)).error(any(), any(), any());
     }
 
-    @Test void multi_hooks_early_out__before() {
+    @Test
+    void multi_hooks_early_out__before() {
         Hook<Boolean> hook = mockBooleanHook();
         Hook<Boolean> hook2 = mockBooleanHook();
         doThrow(RuntimeException.class).when(hook).before(any(), any());
@@ -483,7 +487,8 @@ 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() {
+    @Test
+    void beforeContextUpdated() {
         String targetingKey = "test-key";
         EvaluationContext ctx = new ImmutableContext(targetingKey);
         Hook<Boolean> hook = mockBooleanHook();
@@ -508,15 +513,16 @@ class HookSpecTest implements HookFixtures {
 
     }
 
-    @Specification(number="4.3.5", text="When before hooks have finished executing, any resulting evaluation context MUST be merged with the existing evaluation context.")
-    @Test void mergeHappensCorrectly() {
-        Map<String, Value>  attributes= new HashMap<>();
+    @Specification(number = "4.3.5", text = "When before hooks have finished executing, any resulting evaluation context MUST be merged with the existing evaluation context.")
+    @Test
+    void mergeHappensCorrectly() {
+        Map<String, Value> attributes = new HashMap<>();
         attributes.put("test", new Value("works"));
         attributes.put("another", new Value("exists"));
         EvaluationContext hookCtx = new ImmutableContext(attributes);
 
 
-        Map<String, Value>  attributes1= new HashMap<>();
+        Map<String, Value> attributes1 = new HashMap<>();
         attributes1.put("something", new Value("here"));
         attributes1.put("test", new Value("broken"));
         EvaluationContext invocationCtx = new ImmutableContext(attributes1);
@@ -545,8 +551,9 @@ class HookSpecTest implements HookFixtures {
         assertEquals("here", ec.getValue("something").asString());
     }
 
-    @Specification(number="4.4.3", text="If a finally hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining finally hooks.")
-    @Test void first_finally_broken() {
+    @Specification(number = "4.4.3", text = "If a finally hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining finally hooks.")
+    @Test
+    void first_finally_broken() {
         Hook hook = mockBooleanHook();
         doThrow(RuntimeException.class).when(hook).before(any(), any());
         doThrow(RuntimeException.class).when(hook).finallyAfter(any(), any());
@@ -565,8 +572,9 @@ class HookSpecTest implements HookFixtures {
         order.verify(hook).finallyAfter(any(), any());
     }
 
-    @Specification(number="4.4.4", text="If an error hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining error hooks.")
-    @Test void first_error_broken() {
+    @Specification(number = "4.4.4", text = "If an error hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining error hooks.")
+    @Test
+    void first_error_broken() {
         Hook hook = mockBooleanHook();
         doThrow(RuntimeException.class).when(hook).before(any(), any());
         doThrow(RuntimeException.class).when(hook).error(any(), any(), any());
@@ -588,25 +596,28 @@ class HookSpecTest implements HookFixtures {
     private Client getClient(FeatureProvider provider) {
         OpenFeatureAPI api = OpenFeatureAPI.getInstance();
         if (provider == null) {
-            FeatureProviderTestUtils.setFeatureProvider(new NoOpProvider());
+            FeatureProviderTestUtils.setFeatureProvider(TestEventsProvider.newInitializedTestEventsProvider());
         } else {
             FeatureProviderTestUtils.setFeatureProvider(provider);
         }
         return api.getClient();
     }
 
-    @Specification(number="4.3.1", text="Hooks MUST specify at least one stage.")
-    @Test void default_methods_so_impossible() {}
+    @Specification(number = "4.3.1", text = "Hooks MUST specify at least one stage.")
+    @Test
+    void default_methods_so_impossible() {
+    }
 
-    @Specification(number="4.3.9.1", text="Instead of finally, finallyAfter SHOULD be used.")
+    @Specification(number = "4.3.9.1", text = "Instead of finally, finallyAfter SHOULD be used.")
     @SneakyThrows
-    @Test void doesnt_use_finally() {
+    @Test
+    void doesnt_use_finally() {
         assertThatCode(() -> Hook.class.getMethod("finally", HookContext.class, Map.class))
-            .as("Not possible. Finally is a reserved word.")
-            .isInstanceOf(NoSuchMethodException.class);
+                .as("Not possible. Finally is a reserved word.")
+                .isInstanceOf(NoSuchMethodException.class);
 
         assertThatCode(() -> Hook.class.getMethod("finallyAfter", HookContext.class, Map.class))
-            .doesNotThrowAnyException();
+                .doesNotThrowAnyException();
     }
 
 }
diff --git a/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java b/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java
index 74298f7..23c758e 100644
--- a/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java
+++ b/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java
@@ -1,17 +1,17 @@
 package dev.openfeature.sdk;
 
-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 dev.openfeature.sdk.providers.memory.InMemoryProvider;
+import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
+import dev.openfeature.sdk.testutils.TestEventsProvider;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
 
 import java.util.Collections;
 import java.util.HashMap;
 
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import dev.openfeature.sdk.providers.memory.InMemoryProvider;
-import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 class OpenFeatureAPITest {
 
@@ -33,7 +33,7 @@ class OpenFeatureAPITest {
                 .isEqualTo(api.getProviderMetadata("namedProviderTest").getName());
     }
 
-    @Specification(number="1.1.3", text="The API MUST provide a function to bind a given provider to one or more clients using a domain. If the domain already has a bound provider, it is overwritten with the new mapping.")
+    @Specification(number = "1.1.3", text = "The API MUST provide a function to bind a given provider to one or more clients using a domain. If the domain already has a bound provider, it is overwritten with the new mapping.")
     @Test
     void namedProviderOverwrittenTest() {
         String domain = "namedProviderOverwrittenTest";
@@ -87,4 +87,18 @@ class OpenFeatureAPITest {
         OpenFeatureClient result = client.setEvaluationContext(ctx);
         assertEquals(client, result);
     }
+
+    @Test
+    void getStateReturnsTheStateOfTheAppropriateProvider() throws Exception {
+        String domain = "namedProviderOverwrittenTest";
+        FeatureProvider provider1 = new NoOpProvider();
+        FeatureProvider provider2 = new TestEventsProvider();
+        FeatureProviderTestUtils.setFeatureProvider(domain, provider1);
+        FeatureProviderTestUtils.setFeatureProvider(domain, provider2);
+
+        provider2.initialize(null);
+
+        assertThat(OpenFeatureAPI.getInstance().getClient(domain).getProviderState())
+                .isEqualTo(ProviderState.READY);
+    }
 }
diff --git a/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java b/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java
index d6340a8..82199ac 100644
--- a/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java
+++ b/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java
@@ -1,43 +1,50 @@
 package dev.openfeature.sdk;
 
-import java.util.*;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 
-import dev.openfeature.sdk.fixtures.HookFixtures;
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
 
-import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 import org.simplify4u.slf4jmock.LoggerMock;
 import org.slf4j.Logger;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.*;
+import dev.openfeature.sdk.exceptions.FatalError;
+import dev.openfeature.sdk.fixtures.HookFixtures;
+import dev.openfeature.sdk.testutils.TestEventsProvider;
 
 class OpenFeatureClientTest implements HookFixtures {
 
     private Logger logger;
 
-    @BeforeEach void set_logger() {
+    @BeforeEach
+    void set_logger() {
         logger = Mockito.mock(Logger.class);
         LoggerMock.setMock(OpenFeatureClient.class, logger);
     }
 
-    @AfterEach void reset_logs() {
+    @AfterEach
+    void reset_logs() {
         LoggerMock.setMock(OpenFeatureClient.class, logger);
     }
+
     @Test
     @DisplayName("should not throw exception if hook has different type argument than hookContext")
     void shouldNotThrowExceptionIfHookHasDifferentTypeArgumentThanHookContext() {
-        OpenFeatureAPI api = mock(OpenFeatureAPI.class);
-        when(api.getProvider(any())).thenReturn(new DoSomethingProvider());
-        when(api.getHooks()).thenReturn(Arrays.asList(mockBooleanHook(), mockStringHook()));
-
-        OpenFeatureClient client = new OpenFeatureClient(api, "name", "version");
-
+        OpenFeatureAPI api = OpenFeatureAPI.getInstance();
+        api.setProvider("shouldNotThrowExceptionIfHookHasDifferentTypeArgumentThanHookContext", new DoSomethingProvider());
+        Client client = api.getClient("shouldNotThrowExceptionIfHookHasDifferentTypeArgumentThanHookContext");
+        client.addHooks(mockBooleanHook(), mockStringHook());
         FlagEvaluationDetails<Boolean> actual = client.getBooleanDetails("feature key", Boolean.FALSE);
 
         assertThat(actual.getValue()).isTrue();
@@ -49,31 +56,6 @@ class OpenFeatureClientTest implements HookFixtures {
         Mockito.verify(logger, never()).error(anyString(), any(), any());
     }
 
-    @Test
-    void mergeContextTest() {
-        String flag = "feature key";
-        boolean defaultValue = false;
-        String targetingKey = "targeting key";
-        EvaluationContext ctx = new ImmutableContext(targetingKey, new HashMap<>());
-        OpenFeatureAPI api = mock(OpenFeatureAPI.class);
-        FeatureProvider mockProvider = mock(FeatureProvider.class);
-        // this makes it so that true is returned only if the targeting key set at the client level is honored
-        when(mockProvider.getBooleanEvaluation(
-          eq(flag), eq(defaultValue), argThat(
-            context -> context.getTargetingKey().equals(targetingKey)))).thenReturn(ProviderEvaluation.<Boolean>builder()
-          .value(true).build());
-        when(api.getProvider()).thenReturn(mockProvider);
-        when(api.getProvider(any())).thenReturn(mockProvider);
-
-
-        OpenFeatureClient client = new OpenFeatureClient(api, "name", "version");
-        client.setEvaluationContext(ctx);
-
-        FlagEvaluationDetails<Boolean> result = client.getBooleanDetails(flag, defaultValue);
-
-        assertThat(result.getValue()).isTrue();
-    }
-
     @Test
     @DisplayName("addHooks should allow chaining by returning the same client instance")
     void addHooksShouldAllowChaining() {
@@ -83,7 +65,7 @@ class OpenFeatureClientTest implements HookFixtures {
         Hook<?> hook2 = Mockito.mock(Hook.class);
 
         OpenFeatureClient result = client.addHooks(hook1, hook2);
-        assertEquals(client, result);  
+        assertEquals(client, result);
     }
 
     @Test
@@ -96,5 +78,81 @@ class OpenFeatureClientTest implements HookFixtures {
         OpenFeatureClient result = client.setEvaluationContext(ctx);
         assertEquals(client, result);
     }
-   
+
+    @Test
+    @DisplayName("Should not call evaluation methods when the provider has state FATAL")
+    void shouldNotCallEvaluationMethodsWhenProviderIsInFatalErrorState() {
+        FeatureProvider provider = new TestEventsProvider(100, true, "fake fatal", true);
+        OpenFeatureAPI api = OpenFeatureAPI.getInstance();
+        Client client = api.getClient("shouldNotCallEvaluationMethodsWhenProviderIsInFatalErrorState");
+
+        assertThrows(FatalError.class, () -> api.setProviderAndWait("shouldNotCallEvaluationMethodsWhenProviderIsInFatalErrorState", provider));
+        FlagEvaluationDetails<Boolean> details = client.getBooleanDetails("key", true);
+        assertThat(details.getErrorCode()).isEqualTo(ErrorCode.PROVIDER_FATAL);
+    }
+
+    @Test
+    @DisplayName("Should not call evaluation methods when the provider has state NOT_READY")
+    void shouldNotCallEvaluationMethodsWhenProviderIsInNotReadyState() {
+        FeatureProvider provider = new TestEventsProvider(5000);
+        OpenFeatureAPI api = OpenFeatureAPI.getInstance();
+        api.setProvider("shouldNotCallEvaluationMethodsWhenProviderIsInNotReadyState", provider);
+        Client client = api.getClient("shouldNotCallEvaluationMethodsWhenProviderIsInNotReadyState");
+        FlagEvaluationDetails<Boolean> details = client.getBooleanDetails("key", true);
+
+        assertThat(details.getErrorCode()).isEqualTo(ErrorCode.PROVIDER_NOT_READY);
+    }
+
+    private static class MockProvider implements FeatureProvider {
+        private final AtomicBoolean evaluationCalled = new AtomicBoolean();
+        private final ProviderState providerState;
+
+        public MockProvider(ProviderState providerState) {
+            this.providerState = providerState;
+        }
+
+        public boolean isEvaluationCalled() {
+            return evaluationCalled.get();
+        }
+
+        @Override
+        public ProviderState getState() {
+            return providerState;
+        }
+
+        @Override
+        public Metadata getMetadata() {
+            return null;
+        }
+
+        @Override
+        public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
+            evaluationCalled.set(true);
+            return null;
+        }
+
+        @Override
+        public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
+            evaluationCalled.set(true);
+            return null;
+        }
+
+        @Override
+        public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
+            evaluationCalled.set(true);
+            return null;
+        }
+
+        @Override
+        public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
+            evaluationCalled.set(true);
+            return null;
+        }
+
+        @Override
+        public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) {
+            evaluationCalled.set(true);
+            return null;
+        }
+    }
 }
diff --git a/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java b/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java
index c827c91..26a04d5 100644
--- a/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java
+++ b/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java
@@ -1,19 +1,11 @@
 package dev.openfeature.sdk;
 
-import static dev.openfeature.sdk.fixtures.ProviderFixture.createMockedErrorProvider;
-import static dev.openfeature.sdk.fixtures.ProviderFixture.createMockedProvider;
-import static dev.openfeature.sdk.fixtures.ProviderFixture.createMockedReadyProvider;
-import static dev.openfeature.sdk.testutils.stubbing.ConditionStubber.doDelayResponse;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
-import static org.awaitility.Awaitility.await;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
+import dev.openfeature.sdk.exceptions.OpenFeatureError;
+import dev.openfeature.sdk.testutils.exception.TestException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
 
 import java.time.Duration;
 import java.util.concurrent.ExecutorService;
@@ -23,13 +15,14 @@ import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-
-import dev.openfeature.sdk.exceptions.OpenFeatureError;
-import dev.openfeature.sdk.testutils.exception.TestException;
+import static dev.openfeature.sdk.fixtures.ProviderFixture.*;
+import static dev.openfeature.sdk.testutils.stubbing.ConditionStubber.doDelayResponse;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.awaitility.Awaitility.await;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
 
 class ProviderRepositoryTest {
 
@@ -84,15 +77,6 @@ class ProviderRepositoryTest {
 
                 verify(featureProvider, timeout(TIMEOUT)).initialize(any());
             }
-
-            @Test
-            @DisplayName("should avoid additional initialization call if provider has been initialized already")
-            void shouldAvoidAdditionalInitializationCallIfProviderHasBeenInitializedAlready() throws Exception {
-                FeatureProvider provider = createMockedReadyProvider();
-                setFeatureProvider(provider);
-                
-                verify(provider, never()).initialize(any());
-            }
         }
 
         @Nested
@@ -132,15 +116,6 @@ class ProviderRepositoryTest {
                             return true;
                         });
             }
-
-            @Test
-            @DisplayName("should avoid additional initialization call if provider has been initialized already")
-            void shouldAvoidAdditionalInitializationCallIfProviderHasBeenInitializedAlready() throws Exception {
-                FeatureProvider provider = createMockedReadyProvider();
-                setFeatureProvider(DOMAIN_NAME, provider);
-
-                verify(provider, never()).initialize(any());
-            }
         }
     }
 
@@ -254,11 +229,11 @@ class ProviderRepositoryTest {
                 Consumer<FeatureProvider> afterInit = mock(Consumer.class);
                 Consumer<FeatureProvider> afterShutdown = mock(Consumer.class);
                 BiConsumer<FeatureProvider, OpenFeatureError> afterError = mock(BiConsumer.class);
-        
+
                 FeatureProvider oldProvider = providerRepository.getProvider();
                 FeatureProvider featureProvider1 = createMockedProvider();
                 FeatureProvider featureProvider2 = createMockedProvider();
-        
+
                 setFeatureProvider(featureProvider1, afterSet, afterInit, afterShutdown, afterError);
                 setFeatureProvider(featureProvider2);
                 verify(afterSet, timeout(TIMEOUT)).accept(featureProvider1);
@@ -275,12 +250,13 @@ class ProviderRepositoryTest {
                 Consumer<FeatureProvider> afterInit = mock(Consumer.class);
                 Consumer<FeatureProvider> afterShutdown = mock(Consumer.class);
                 BiConsumer<FeatureProvider, OpenFeatureError> afterError = mock(BiConsumer.class);
-        
+
                 FeatureProvider errorFeatureProvider = createMockedErrorProvider();
-        
+
                 setFeatureProvider(errorFeatureProvider, afterSet, afterInit, afterShutdown, afterError);
                 verify(afterSet, timeout(TIMEOUT)).accept(errorFeatureProvider);
-                verify(afterInit, never()).accept(any());;
+                verify(afterInit, never()).accept(any());
+                ;
                 verify(afterError, timeout(TIMEOUT)).accept(eq(errorFeatureProvider), any());
             }
         }
@@ -309,8 +285,8 @@ class ProviderRepositoryTest {
 
 
     private void setFeatureProvider(FeatureProvider provider, Consumer<FeatureProvider> afterSet,
-            Consumer<FeatureProvider> afterInit, Consumer<FeatureProvider> afterShutdown,
-            BiConsumer<FeatureProvider, OpenFeatureError> afterError) {
+                                    Consumer<FeatureProvider> afterInit, Consumer<FeatureProvider> afterShutdown,
+                                    BiConsumer<FeatureProvider, OpenFeatureError> afterError) {
         providerRepository.setProvider(provider, afterSet, afterInit, afterShutdown,
                 afterError, false);
         waitForSettingProviderHasBeenCompleted(ProviderRepository::getProvider, provider);
@@ -329,7 +305,7 @@ class ProviderRepositoryTest {
                 .pollDelay(Duration.ofMillis(1))
                 .atMost(Duration.ofSeconds(5))
                 .until(() -> {
-                    return extractor.apply(providerRepository) == provider;
+                    return extractor.apply(providerRepository).equals(provider);
                 });
     }
 
diff --git a/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java b/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java
index cb7770d..55ddc07 100644
--- a/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java
+++ b/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java
@@ -1,12 +1,9 @@
 package dev.openfeature.sdk.providers.memory;
 
 import com.google.common.collect.ImmutableMap;
-import dev.openfeature.sdk.Client;
-import dev.openfeature.sdk.EventDetails;
-import dev.openfeature.sdk.ImmutableContext;
-import dev.openfeature.sdk.OpenFeatureAPI;
-import dev.openfeature.sdk.Value;
+import dev.openfeature.sdk.*;
 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.SneakyThrows;
@@ -19,16 +16,11 @@ import java.util.function.Consumer;
 
 import static dev.openfeature.sdk.Structure.mapToStructure;
 import static dev.openfeature.sdk.testutils.TestFlagsUtils.buildFlags;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.*;
 
 class InMemoryProviderTest {
 
@@ -41,21 +33,16 @@ class InMemoryProviderTest {
     void beforeEach() {
         Map<String, Flag<?>> flags = buildFlags();
         provider = spy(new InMemoryProvider(flags));
-        OpenFeatureAPI.getInstance().onProviderConfigurationChanged(eventDetails -> {});
+        OpenFeatureAPI.getInstance().onProviderConfigurationChanged(eventDetails -> {
+        });
         OpenFeatureAPI.getInstance().setProviderAndWait(provider);
         client = OpenFeatureAPI.getInstance().getClient();
         provider.updateFlags(flags);
         provider.updateFlag("addedFlag", Flag.builder()
-            .variant("on", true)
-            .variant("off", false)
-            .defaultVariant("on")
-            .build());
-    }
-
-    @SneakyThrows
-    @Test
-    void eventsTest() {
-        verify(provider, times(2)).emitProviderConfigurationChanged(any());
+                .variant("on", true)
+                .variant("off", false)
+                .defaultVariant("on")
+                .build());
     }
 
     @Test
@@ -81,9 +68,9 @@ class InMemoryProviderTest {
     @Test
     void getObjectEvaluation() {
         Value expectedObject = new Value(mapToStructure(ImmutableMap.of(
-            "showImages", new Value(true),
-            "title", new Value("Check out these pics!"),
-            "imagesPerPage", new Value(100)
+                "showImages", new Value(true),
+                "title", new Value("Check out these pics!"),
+                "imagesPerPage", new Value(100)
         )));
         assertEquals(expectedObject, client.getObjectValue("object-flag", new Value(true)));
     }
@@ -108,7 +95,7 @@ class InMemoryProviderTest {
         InMemoryProvider inMemoryProvider = new InMemoryProvider(new HashMap<>());
 
         // ErrorCode.PROVIDER_NOT_READY should be returned when evaluated via the client
-        assertThrows(ProviderNotReadyError.class, ()-> inMemoryProvider.getBooleanEvaluation("fail_not_initialized", false, new ImmutableContext()));
+        assertThrows(ProviderNotReadyError.class, () -> inMemoryProvider.getBooleanEvaluation("fail_not_initialized", false, new ImmutableContext()));
     }
 
     @SuppressWarnings("unchecked")
diff --git a/src/test/java/dev/openfeature/sdk/testutils/FeatureProviderTestUtils.java b/src/test/java/dev/openfeature/sdk/testutils/FeatureProviderTestUtils.java
index 41ffbe1..12fb71b 100644
--- a/src/test/java/dev/openfeature/sdk/testutils/FeatureProviderTestUtils.java
+++ b/src/test/java/dev/openfeature/sdk/testutils/FeatureProviderTestUtils.java
@@ -21,7 +21,7 @@ public class FeatureProviderTestUtils {
         await()
             .pollDelay(Duration.ofMillis(1))
             .atMost(Duration.ofSeconds(1))
-            .until(() -> extractor.apply(OpenFeatureAPI.getInstance()) == provider);
+            .until(() -> extractor.apply(OpenFeatureAPI.getInstance()).equals(provider));
     }
 
     public static void setFeatureProvider(String domain, FeatureProvider provider) {
diff --git a/src/test/java/dev/openfeature/sdk/testutils/TestEventsProvider.java b/src/test/java/dev/openfeature/sdk/testutils/TestEventsProvider.java
index 0b19f82..1944fce 100644
--- a/src/test/java/dev/openfeature/sdk/testutils/TestEventsProvider.java
+++ b/src/test/java/dev/openfeature/sdk/testutils/TestEventsProvider.java
@@ -1,28 +1,22 @@
 package dev.openfeature.sdk.testutils;
 
-import dev.openfeature.sdk.EvaluationContext;
-import dev.openfeature.sdk.EventProvider;
-import dev.openfeature.sdk.Metadata;
-import dev.openfeature.sdk.ProviderEvaluation;
-import dev.openfeature.sdk.ProviderEvent;
-import dev.openfeature.sdk.ProviderEventDetails;
-import dev.openfeature.sdk.ProviderState;
-import dev.openfeature.sdk.Value;
+import dev.openfeature.sdk.*;
+import dev.openfeature.sdk.exceptions.FatalError;
 import dev.openfeature.sdk.exceptions.GeneralError;
+import lombok.SneakyThrows;
 
 public class TestEventsProvider extends EventProvider {
+    public static final String PASSED_IN_DEFAULT = "Passed in default";
 
     private boolean initError = false;
     private String initErrorMessage;
-    private ProviderState state = ProviderState.NOT_READY;
     private boolean shutDown = false;
     private int initTimeoutMs = 0;
     private String name = "test";
     private Metadata metadata = () -> name;
+    private boolean isFatalInitError = false;
 
-    @Override
-    public ProviderState getState() {
-        return this.state;
+    public TestEventsProvider() {
     }
 
     public TestEventsProvider(int initTimeoutMs) {
@@ -35,8 +29,18 @@ public class TestEventsProvider extends EventProvider {
         this.initErrorMessage = initErrorMessage;
     }
 
-    public TestEventsProvider(ProviderState initialState) {
-        this.state = initialState;
+    public TestEventsProvider(int initTimeoutMs, boolean initError, String initErrorMessage, boolean fatal) {
+        this.initTimeoutMs = initTimeoutMs;
+        this.initError = initError;
+        this.initErrorMessage = initErrorMessage;
+        this.isFatalInitError = fatal;
+    }
+
+    @SneakyThrows
+    public static TestEventsProvider newInitializedTestEventsProvider() {
+        TestEventsProvider provider = new TestEventsProvider();
+        provider.initialize(null);
+        return provider;
     }
 
     public void mockEvent(ProviderEvent event, ProviderEventDetails details) {
@@ -54,14 +58,13 @@ public class TestEventsProvider extends EventProvider {
 
     @Override
     public void initialize(EvaluationContext evaluationContext) throws Exception {
-        if (ProviderState.NOT_READY.equals(state)) {
-            // wait half the TIMEOUT, otherwise some init/errors can be fired before we add handlers
-            Thread.sleep(initTimeoutMs);
-            if (this.initError) {
-                this.state = ProviderState.ERROR;
-                throw new GeneralError(initErrorMessage);
+        // wait half the TIMEOUT, otherwise some init/errors can be fired before we add handlers
+        Thread.sleep(initTimeoutMs);
+        if (this.initError) {
+            if (this.isFatalInitError) {
+                throw new FatalError(initErrorMessage);
             }
-            this.state = ProviderState.READY;
+            throw new GeneralError(initErrorMessage);
         }
     }
 
@@ -71,32 +74,48 @@ public class TestEventsProvider extends EventProvider {
     }
 
     @Override
-    public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue,
-            EvaluationContext ctx) {
-        throw new UnsupportedOperationException("Unimplemented method 'getBooleanEvaluation'");
+    public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
+        return ProviderEvaluation.<Boolean>builder()
+                .value(defaultValue)
+                .variant(PASSED_IN_DEFAULT)
+                .reason(Reason.DEFAULT.toString())
+                .build();
     }
 
     @Override
-    public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue,
-            EvaluationContext ctx) {
-        throw new UnsupportedOperationException("Unimplemented method 'getStringEvaluation'");
+    public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
+        return ProviderEvaluation.<String>builder()
+                .value(defaultValue)
+                .variant(PASSED_IN_DEFAULT)
+                .reason(Reason.DEFAULT.toString())
+                .build();
     }
 
     @Override
-    public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue,
-            EvaluationContext ctx) {
-        throw new UnsupportedOperationException("Unimplemented method 'getIntegerEvaluation'");
+    public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
+        return ProviderEvaluation.<Integer>builder()
+                .value(defaultValue)
+                .variant(PASSED_IN_DEFAULT)
+                .reason(Reason.DEFAULT.toString())
+                .build();
     }
 
     @Override
-    public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue,
-            EvaluationContext ctx) {
-        throw new UnsupportedOperationException("Unimplemented method 'getDoubleEvaluation'");
+    public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
+        return ProviderEvaluation.<Double>builder()
+                .value(defaultValue)
+                .variant(PASSED_IN_DEFAULT)
+                .reason(Reason.DEFAULT.toString())
+                .build();
     }
 
     @Override
     public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue,
-            EvaluationContext ctx) {
-        throw new UnsupportedOperationException("Unimplemented method 'getObjectEvaluation'");
+                                                         EvaluationContext invocationContext) {
+        return ProviderEvaluation.<Value>builder()
+                .value(defaultValue)
+                .variant(PASSED_IN_DEFAULT)
+                .reason(Reason.DEFAULT.toString())
+                .build();
     }
-};
\ No newline at end of file
+}