Skip to content

Fix: client breaks when evaluation contains segments or unknown response fields#5

Merged
liamhughes merged 12 commits intomainfrom
liamhughes/devex-80-java-client-breaks-on-segments-and-new-response-fields
Mar 27, 2026
Merged

Fix: client breaks when evaluation contains segments or unknown response fields#5
liamhughes merged 12 commits intomainfrom
liamhughes/devex-80-java-client-breaks-on-segments-and-new-response-fields

Conversation

@liamhughes
Copy link
Copy Markdown
Contributor

@liamhughes liamhughes commented Mar 25, 2026

Background

The provider was failing to deserialise evaluation API responses in two scenarios:

  1. If segments were present
  2. New (or otherwise unknown) response fields

https://linear.app/octopus/issue/DEVEX-80/java-client-breaks-on-segments-and-new-response-fields

Changes

  • Added Segment class
    • Replaces Map.Entry<String, String> as the type for segment data, with required key and value fields.
    • Uses @JsonCreator/@JsonProperty for deserialisation.
  • Added OctopusObjectMapper
    • Wraps a shared, static ObjectMapper instance configured with case-insensitive property matching and FAIL_ON_UNKNOWN_PROPERTIES disabled, so API responses with differing capitalisation or new fields deserialise correctly.
    • ObjectMapper is static to avoid the cost of repeated instantiation.
  • getSegments() returns an immutable list.
  • Added FeatureToggleEvaluationDeserializationTests
    • Unit tests covering: enabled/disabled toggles, null or missing segments field, toggles with one or more segments, a list of toggles, responses containing extraneous/unknown properties, and responses with differing field capitalisation.
    • Note: some these tests can perhaps be removed when we add the new specification tests.

Notes for review

  • I have opted to not implement equals/hashCode on Segment because it's verbose and not needed at this time. I think we can rely on tests to make sure we don't get caught out, but happy for a difference of opinion here.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes Jackson deserialization failures in the provider client when the evaluation response includes segments or introduces previously-unknown fields, by adding targeted Jackson configuration and tests.

Changes:

  • Added a custom Jackson SegmentDeserializer to map { "key": "...", "value": "..." } into Map.Entry<String,String>.
  • Updated FeatureToggleEvaluation to (a) ignore unknown response properties and (b) wire the custom deserializer for segments.
  • Added unit tests + JSON fixtures covering segments, missing/null segments, list responses, and extraneous properties.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

File Description
src/main/java/com/octopus/openfeature/provider/SegmentDeserializer.java Adds custom element deserialization for segments entries.
src/main/java/com/octopus/openfeature/provider/FeatureToggleEvaluation.java Ignores unknown fields and applies the segment entry deserializer.
src/test/java/com/octopus/openfeature/provider/FeatureToggleEvaluationDeserializationTests.java Adds coverage for the previously failing deserialization scenarios.
src/test/resources/com/octopus/openfeature/provider/*.json Adds fixture payloads for the deserialization tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@liamhughes liamhughes marked this pull request as ready for review March 25, 2026 23:32
@liamhughes liamhughes requested review from a team and Copilot March 25, 2026 23:34
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

dylanlerch
dylanlerch previously approved these changes Mar 26, 2026
Copy link
Copy Markdown

@dylanlerch dylanlerch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. One question about case-insensitivity.

return null;
}

JsonNode keyNode = node.get("key");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this case-insensitive?

Copy link
Copy Markdown

@dylanlerch dylanlerch Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And can we add a test for it to make sure that it is (I couldn't find one but I also might just be a silly boy that missed it).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To answer your question: none of this JSON deserialisation was case insensitive.

Implementing case insensitivity led me to two major changes:

  1. a shared ObjectMapper which also replaced the previous solution to extraneous fields
  2. introducing a Segment type rather than further complicate the SegmentDeserializer

@liamhughes liamhughes dismissed dylanlerch’s stale review March 26, 2026 04:40

Significant changes since.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +8 to +12
class OctopusObjectMapper {
static final ObjectMapper INSTANCE = JsonMapper.builder()
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OctopusObjectMapper.INSTANCE is a mutable ObjectMapper that can be reconfigured by any code in the package, which can lead to surprising cross-test or runtime behavior. Consider making the class final with a private constructor and exposing the mapper via a method that returns the shared instance (or a copy) while preventing external reconfiguration.

Suggested change
class OctopusObjectMapper {
static final ObjectMapper INSTANCE = JsonMapper.builder()
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();
final class OctopusObjectMapper {
private static final ObjectMapper INSTANCE = JsonMapper.builder()
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();
private OctopusObjectMapper() {
// Utility class; prevent instantiation.
}
static ObjectMapper getInstance() {
// Return a defensive copy to avoid shared mutable configuration.
return INSTANCE.copy();
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm...the whole point was to reuse the same instance. Making the wrapper final wouldn't hurt, but seems unnecessary?


import java.io.InputStream;
import java.util.List;
import java.util.Map;
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import java.util.Map can be removed to keep the test focused and avoid IDE/linter noise.

Suggested change
import java.util.Map;

Copilot uses AI. Check for mistakes.
@liamhughes liamhughes merged commit ce8282e into main Mar 27, 2026
9 checks passed
@liamhughes liamhughes deleted the liamhughes/devex-80-java-client-breaks-on-segments-and-new-response-fields branch March 27, 2026 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants