Repository: open-feature/java-sdk
Pull request #459 (2023-05-31 18:38:48)

feat: add flag metadata
## This PR

Is connected to #437 & completes the implementation of flag metadata spec implementation [1]

The implementation is backed by immutable `FlagMetadata`. If present, metadata gets propagated from `ProviderEvaluation` to `FlagEvaluationDetails`

Tests were added to validate the construction as well as the propagation 


[1] - https://github.com/open-feature/spec/commit/74c373e089ad77bf8cac84f3d93c00c945ff3a8a

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=459)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL) [3 Code Smells](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=459)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL) [3 Code Smells](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459&metric=new_duplicated_lines_density&view=list)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/open-feature/java-sdk/pull/459?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) Report
> Merging [#459](https://app.codecov.io/gh/open-feature/java-sdk/pull/459?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) (676cf5a) into [main](https://app.codecov.io/gh/open-feature/java-sdk/commit/e1a0432ae988c5311bc00008fd3e8687d3a3839f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) (e1a0432) will **increase** coverage by `93.59%`.
> The diff coverage is `100.00%`.

> :exclamation: Current head 676cf5a differs from pull request most recent head ec8c6f9. Consider uploading reports for the commit ec8c6f9 to get more accurate results

```diff
@@             Coverage Diff             @@
##             main     #459       +/-   ##
===========================================
+ Coverage        0   93.59%   +93.59%     
- Complexity      0      229      +229     
===========================================
  Files           0       24       +24     
  Lines           0      531      +531     
  Branches        0       33       +33     
===========================================
+ Hits            0      497      +497     
- Misses          0       17       +17     
- Partials        0       17       +17     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `93.59% <100.00%> (?)` | |

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.

| [Impacted Files](https://app.codecov.io/gh/open-feature/java-sdk/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) | Coverage Δ | |
|---|---|---|
| [...ava/dev/openfeature/sdk/FlagEvaluationDetails.java](https://app.codecov.io/gh/open-feature/java-sdk/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature#diff-c3JjL21haW4vamF2YS9kZXYvb3BlbmZlYXR1cmUvc2RrL0ZsYWdFdmFsdWF0aW9uRGV0YWlscy5qYXZh) | `100.00% <100.00%> (ø)` | |
| [...rc/main/java/dev/openfeature/sdk/FlagMetadata.java](https://app.codecov.io/gh/open-feature/java-sdk/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature#diff-c3JjL21haW4vamF2YS9kZXYvb3BlbmZlYXR1cmUvc2RrL0ZsYWdNZXRhZGF0YS5qYXZh) | `100.00% <100.00%> (ø)` | |

... and [22 files with indirect coverage changes](https://app.codecov.io/gh/open-feature/java-sdk/pull/459/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature)

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature)

toddbaert: @Kavindu-Dodan this all looks good to me, except that I'm not sure about [throwing on failed retrieval](https://github.com/open-feature/java-sdk/pull/459#discussion_r1212183917).

This seems like a typical eventuality, not an exceptional one.
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=459)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL) [3 Code Smells](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=459)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL) [3 Code Smells](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=459)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL) [3 Code Smells](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459&metric=new_duplicated_lines_density&view=list)


Kavindu-Dodan: > @Kavindu-Dodan this all looks good to me, except that I'm not sure about [throwing on failed retrieval](https://github.com/open-feature/java-sdk/pull/459#discussion_r1212183917).
> 
> This seems like a typical eventuality, not an exceptional one.

Updated the implementation to avoid exceptions. Instead now we return null for missing key & value type differences , along with debug level log entry
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=459)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL) [3 Code Smells](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=open-feature_java-sdk&pullRequest=459)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL) [4 Code Smells](https://sonarcloud.io/project/issues?id=open-feature_java-sdk&pullRequest=459&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=open-feature_java-sdk&pullRequest=459&metric=new_duplicated_lines_density&view=list)


toddbaert: @Kavindu-Dodan I rebased on main and resolved conflicts with @lopitz 's recent PR. I hope you don't mind. This looks good to me, and we have lots of approvals so I'm merging.

Patch:
diff --git a/src/main/java/dev/openfeature/sdk/FlagEvaluationDetails.java b/src/main/java/dev/openfeature/sdk/FlagEvaluationDetails.java
index 67ee853..24e6a39 100644
--- a/src/main/java/dev/openfeature/sdk/FlagEvaluationDetails.java
+++ b/src/main/java/dev/openfeature/sdk/FlagEvaluationDetails.java
@@ -7,23 +7,27 @@ import javax.annotation.Nullable;
 
 /**
  * Contains information about how the evaluation happened, including any resolved values.
+ *
  * @param <T> the type of the flag being evaluated.
  */
-@Data @Builder
+@Data
+@Builder
 public class FlagEvaluationDetails<T> implements BaseEvaluation<T> {
+
     private String flagKey;
     private T value;
     @Nullable private String variant;
     @Nullable private String reason;
     private ErrorCode errorCode;
     @Nullable private String errorMessage;
+    @Builder.Default private FlagMetadata flagMetadata = FlagMetadata.builder().build();
 
     /**
      * Generate detail payload from the provider response.
      *
      * @param providerEval provider response
-     * @param flagKey key for the flag being evaluated
-     * @param <T> type of flag being returned
+     * @param flagKey      key for the flag being evaluated
+     * @param <T>          type of flag being returned
      * @return detail payload
      */
     public static <T> FlagEvaluationDetails<T> from(ProviderEvaluation<T> providerEval, String flagKey) {
@@ -33,6 +37,7 @@ public class FlagEvaluationDetails<T> implements BaseEvaluation<T> {
                 .variant(providerEval.getVariant())
                 .reason(providerEval.getReason())
                 .errorCode(providerEval.getErrorCode())
+                .flagMetadata(providerEval.getFlagMetadata())
                 .build();
     }
 }
diff --git a/src/main/java/dev/openfeature/sdk/FlagMetadata.java b/src/main/java/dev/openfeature/sdk/FlagMetadata.java
new file mode 100644
index 0000000..e9b6ccb
--- /dev/null
+++ b/src/main/java/dev/openfeature/sdk/FlagMetadata.java
@@ -0,0 +1,188 @@
+package dev.openfeature.sdk;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Immutable Flag Metadata representation. Implementation is backed by a {@link Map} and immutability is provided
+ * through builder and accessors.
+ */
+@Slf4j
+public class FlagMetadata {
+    private final Map<String, Object> metadata;
+
+    private FlagMetadata(Map<String, Object> metadata) {
+        this.metadata = metadata;
+    }
+
+    /**
+     * Retrieve a {@link String} value for the given key. A {@code null} value is returned if the key does not exist
+     * or if the value is of a different type.
+     *
+     * @param key flag metadata key to retrieve
+     */
+    public String getString(final String key) {
+        return getValue(key, String.class);
+    }
+
+    /**
+     * Retrieve a {@link Integer} value for the given key. A {@code null} value is returned if the key does not exist
+     * or if the value is of a different type.
+     *
+     * @param key flag metadata key to retrieve
+     */
+    public Integer getInteger(final String key) {
+        return getValue(key, Integer.class);
+    }
+
+    /**
+     * Retrieve a {@link Long} value for the given key. A {@code null} value is returned if the key does not exist
+     * or if the value is of a different type.
+     *
+     * @param key flag metadata key to retrieve
+     */
+    public Long getLong(final String key) {
+        return getValue(key, Long.class);
+    }
+
+    /**
+     * Retrieve a {@link Float} value for the given key. A {@code null} value is returned if the key does not exist
+     * or if the value is of a different type.
+     *
+     * @param key flag metadata key to retrieve
+     */
+    public Float getFloat(final String key) {
+        return getValue(key, Float.class);
+    }
+
+    /**
+     * Retrieve a {@link Double} value for the given key. A {@code null} value is returned if the key does not exist
+     * or if the value is of a different type.
+     *
+     * @param key flag metadata key to retrieve
+     */
+    public Double getDouble(final String key) {
+        return getValue(key, Double.class);
+    }
+
+    /**
+     * Retrieve a {@link Boolean} value for the given key. A {@code null} value is returned if the key does not exist
+     * or if the value is of a different type.
+     *
+     * @param key flag metadata key to retrieve
+     */
+    public Boolean getBoolean(final String key) {
+        return getValue(key, Boolean.class);
+    }
+
+    private <T> T getValue(final String key, final Class<T> type) {
+        final Object o = metadata.get(key);
+
+        if (o == null) {
+            log.debug("Metadata key " + key + "does not exist");
+            return null;
+        }
+
+        try {
+            return type.cast(o);
+        } catch (ClassCastException e) {
+            log.debug("Error retrieving value for key " + key, e);
+            return null;
+        }
+    }
+
+
+    /**
+     * Obtain a builder for {@link FlagMetadata}.
+     */
+    public static FlagMetadataBuilder builder() {
+        return new FlagMetadataBuilder();
+    }
+
+    /**
+     * Immutable builder for {@link FlagMetadata}.
+     */
+    public static class FlagMetadataBuilder {
+        private final Map<String, Object> metadata;
+
+        private FlagMetadataBuilder() {
+            metadata = new HashMap<>();
+        }
+
+        /**
+         * Add String value to the metadata.
+         *
+         * @param key   flag metadata key to add
+         * @param value flag metadata value to add
+         */
+        public FlagMetadataBuilder addString(final String key, final String value) {
+            metadata.put(key, value);
+            return this;
+        }
+
+        /**
+         * Add Integer value to the metadata.
+         *
+         * @param key   flag metadata key to add
+         * @param value flag metadata value to add
+         */
+        public FlagMetadataBuilder addInteger(final String key, final Integer value) {
+            metadata.put(key, value);
+            return this;
+        }
+
+        /**
+         * Add Long value to the metadata.
+         *
+         * @param key   flag metadata key to add
+         * @param value flag metadata value to add
+         */
+        public FlagMetadataBuilder addLong(final String key, final Long value) {
+            metadata.put(key, value);
+            return this;
+        }
+
+        /**
+         * Add Float value to the metadata.
+         *
+         * @param key   flag metadata key to add
+         * @param value flag metadata value to add
+         */
+        public FlagMetadataBuilder addFloat(final String key, final Float value) {
+            metadata.put(key, value);
+            return this;
+        }
+
+        /**
+         * Add Double value to the metadata.
+         *
+         * @param key   flag metadata key to add
+         * @param value flag metadata value to add
+         */
+        public FlagMetadataBuilder addDouble(final String key, final Double value) {
+            metadata.put(key, value);
+            return this;
+        }
+
+        /**
+         * Add Boolean value to the metadata.
+         *
+         * @param key   flag metadata key to add
+         * @param value flag metadata value to add
+         */
+        public FlagMetadataBuilder addBoolean(final String key, final Boolean value) {
+            metadata.put(key, value);
+            return this;
+        }
+
+        /**
+         * Retrieve {@link FlagMetadata} with provided key,value pairs.
+         */
+        public FlagMetadata build() {
+            return new FlagMetadata(this.metadata);
+        }
+
+    }
+}
diff --git a/src/main/java/dev/openfeature/sdk/ProviderEvaluation.java b/src/main/java/dev/openfeature/sdk/ProviderEvaluation.java
index 9ba1ab9..ffa5c3c 100644
--- a/src/main/java/dev/openfeature/sdk/ProviderEvaluation.java
+++ b/src/main/java/dev/openfeature/sdk/ProviderEvaluation.java
@@ -13,4 +13,6 @@ public class ProviderEvaluation<T> implements BaseEvaluation<T> {
     @Nullable private String reason;
     ErrorCode errorCode;
     @Nullable private String errorMessage;
+    @Builder.Default
+    private FlagMetadata flagMetadata = FlagMetadata.builder().build();
 }


Test patch:
diff --git a/src/test/java/dev/openfeature/sdk/DoSomethingProvider.java b/src/test/java/dev/openfeature/sdk/DoSomethingProvider.java
index d87fa37..26e6737 100644
--- a/src/test/java/dev/openfeature/sdk/DoSomethingProvider.java
+++ b/src/test/java/dev/openfeature/sdk/DoSomethingProvider.java
@@ -1,11 +1,14 @@
 package dev.openfeature.sdk;
 
-public class DoSomethingProvider implements FeatureProvider {
+class DoSomethingProvider implements FeatureProvider {
+
+    static final String name = "Something";
+    // Flag evaluation metadata
+    static final FlagMetadata flagMetadata = FlagMetadata.builder().build();
 
-    public static final String name = "Something";
     private EvaluationContext savedContext;
 
-    public EvaluationContext getMergedContext() {
+    EvaluationContext getMergedContext() {
         return savedContext;
     }
 
@@ -18,13 +21,16 @@ public class DoSomethingProvider implements FeatureProvider {
     public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
         savedContext = ctx;
         return ProviderEvaluation.<Boolean>builder()
-                .value(!defaultValue).build();
+                .value(!defaultValue)
+                .flagMetadata(flagMetadata)
+                .build();
     }
 
     @Override
     public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
         return ProviderEvaluation.<String>builder()
                 .value(new StringBuilder(defaultValue).reverse().toString())
+                .flagMetadata(flagMetadata)
                 .build();
     }
 
@@ -33,6 +39,7 @@ public class DoSomethingProvider implements FeatureProvider {
         savedContext = ctx;
         return ProviderEvaluation.<Integer>builder()
                 .value(defaultValue * 100)
+                .flagMetadata(flagMetadata)
                 .build();
     }
 
@@ -41,6 +48,7 @@ public class DoSomethingProvider implements FeatureProvider {
         savedContext = ctx;
         return ProviderEvaluation.<Double>builder()
                 .value(defaultValue * 100)
+                .flagMetadata(flagMetadata)
                 .build();
     }
 
@@ -49,6 +57,7 @@ public class DoSomethingProvider implements FeatureProvider {
         savedContext = invocationContext;
         return ProviderEvaluation.<Value>builder()
                 .value(null)
+                .flagMetadata(flagMetadata)
                 .build();
     }
 }
diff --git a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
index e508bdd..57f0c04 100644
--- a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
+++ b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
@@ -1,6 +1,8 @@
 package dev.openfeature.sdk;
 
-import static org.assertj.core.api.Assertions.*;
+import static dev.openfeature.sdk.DoSomethingProvider.flagMetadata;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -15,18 +17,18 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import dev.openfeature.sdk.exceptions.FlagNotFoundError;
-import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-
-import dev.openfeature.sdk.fixtures.HookFixtures;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mockito;
 import org.simplify4u.slf4jmock.LoggerMock;
 import org.slf4j.Logger;
 
+import dev.openfeature.sdk.exceptions.FlagNotFoundError;
+import dev.openfeature.sdk.fixtures.HookFixtures;
+import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
+
 class FlagEvaluationSpecTest implements HookFixtures {
 
     private Logger logger;
@@ -150,6 +152,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
                 .flagKey(key)
                 .value(false)
                 .variant(null)
+                .flagMetadata(flagMetadata)
                 .build();
         assertEquals(bd, c.getBooleanDetails(key, true));
         assertEquals(bd, c.getBooleanDetails(key, true, new ImmutableContext()));
@@ -159,6 +162,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
                 .flagKey(key)
                 .value("tset")
                 .variant(null)
+                .flagMetadata(flagMetadata)
                 .build();
         assertEquals(sd, c.getStringDetails(key, "test"));
         assertEquals(sd, c.getStringDetails(key, "test", new ImmutableContext()));
@@ -167,6 +171,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
         FlagEvaluationDetails<Integer> id = FlagEvaluationDetails.<Integer>builder()
                 .flagKey(key)
                 .value(400)
+                .flagMetadata(flagMetadata)
                 .build();
         assertEquals(id, c.getIntegerDetails(key, 4));
         assertEquals(id, c.getIntegerDetails(key, 4, new ImmutableContext()));
@@ -175,6 +180,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
         FlagEvaluationDetails<Double> dd = FlagEvaluationDetails.<Double>builder()
                 .flagKey(key)
                 .value(40.0)
+                .flagMetadata(flagMetadata)
                 .build();
         assertEquals(dd, c.getDoubleDetails(key, .4));
         assertEquals(dd, c.getDoubleDetails(key, .4, new ImmutableContext()));
diff --git a/src/test/java/dev/openfeature/sdk/FlagMetadataTest.java b/src/test/java/dev/openfeature/sdk/FlagMetadataTest.java
new file mode 100644
index 0000000..b52eb6d
--- /dev/null
+++ b/src/test/java/dev/openfeature/sdk/FlagMetadataTest.java
@@ -0,0 +1,53 @@
+package dev.openfeature.sdk;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class FlagMetadataTest {
+
+    @Test
+    @DisplayName("Test metadata payload construction and retrieval")
+    public void builder_validation() {
+        // given
+        FlagMetadata flagMetadata = FlagMetadata.builder()
+                .addString("string", "string")
+                .addInteger("integer", 1)
+                .addLong("long", 1L)
+                .addFloat("float", 1.5f)
+                .addDouble("double", Double.MAX_VALUE)
+                .addBoolean("boolean", Boolean.FALSE)
+                .build();
+
+        // then
+        assertThat(flagMetadata.getString("string")).isEqualTo("string");
+        assertThat(flagMetadata.getInteger("integer")).isEqualTo(1);
+        assertThat(flagMetadata.getLong("long")).isEqualTo(1L);
+        assertThat(flagMetadata.getFloat("float")).isEqualTo(1.5f);
+        assertThat(flagMetadata.getDouble("double")).isEqualTo(Double.MAX_VALUE);
+        assertThat(flagMetadata.getBoolean("boolean")).isEqualTo(Boolean.FALSE);
+    }
+
+    @Test
+    @DisplayName("Value type mismatch returns a null")
+    public void value_type_validation() {
+        // given
+        FlagMetadata flagMetadata = FlagMetadata.builder()
+                .addString("string", "string")
+                .build();
+
+        // then
+       assertThat(flagMetadata.getBoolean("string")).isNull();
+    }
+
+    @Test
+    @DisplayName("A null is returned if key does not exist")
+    public void notfound_error_validation() {
+        // given
+        FlagMetadata flagMetadata = FlagMetadata.builder().build();
+
+        // then
+        assertThat(flagMetadata.getBoolean("string")).isNull();
+    }
+}