Skip to content

Instantly share code, notes, and snippets.

@joshgord
Last active January 31, 2026 02:23
Show Gist options
  • Select an option

  • Save joshgord/dfa7e5f17f85b1c73fe6d97606470b1e to your computer and use it in GitHub Desktop.

Select an option

Save joshgord/dfa7e5f17f85b1c73fe6d97606470b1e to your computer and use it in GitHub Desktop.
UiFlavor to ClientFlavor Migration Plan

UiFlavor to ClientFlavor Migration Plan

Overview

This document describes the migration plan for transitioning from request-attrs UiFlavor to microcontext ClientFlavor as the source of truth for client/UI flavor identification.

Background

Current State

Two separate enum definitions exist for client flavors:

Library Enum Package Location
request-attrs UiFlavor com.netflix.request.protogen request-attrs-api/src/main/proto/com/netflix/request/ui_flavor.proto
microcontext ClientFlavor netflix.context.client.flavor microcontext-model/src/main/proto/netflix/context/client/flavor/flavor.proto

Why Migrate?

  • Single Source of Truth: Having two enum definitions leads to drift and maintenance burden
  • Microcontext is the Standard: Microcontext is the canonical context propagation library at Netflix
  • Deprecation Path: request-attrs flavor support can be gradually deprecated

Enum Name Mapping

Conversions are performed by name (not numeric value), ensuring compatibility regardless of proto index differences.

request-attrs UiFlavor microcontext ClientFlavor Notes
UNKNOWN_UI_FLAVOR UNSPECIFIED Default/unknown
AKIRA AKIRA Web
ANDROID ANDROID Android mobile
ARGO ARGO iOS mobile
ATV_FUJI ATV_FUJI Apple TV 2 & 3
ATV_HOPPER ATV_HOPPER Apple TV 2015
DARWIN DARWIN TV platforms
IOS_LEGACY IOS_LEGACY Legacy iOS
TV_OTHER TV_OTHER Other TV devices
WINDOWS_COUGAR WINDOWS_COUGAR Windows Phone (deprecated)
WINDOWS_GOTHAM WINDOWS_GOTHAM Windows 8 (deprecated)
WINDOWS_PX WINDOWS_PX Windows 10 (deprecated)
WINDOWS_WILDCAT WINDOWS_WILDCAT Windows Phone 8.1 (deprecated)
ECLIPSE ECLIPSE TV UI
ATV_ECLIPSE ATV_ECLIPSE Apple TV Eclipse

Key Points

  1. Name-based conversion: All conversions use enum names, not numeric proto values
  2. FAKIRA: Microcontext-only value that maps to AKIRA (web) when converting to UiFlavor
  3. Microcontext-only values: The following only exist in ClientFlavor and map to UNKNOWN_UI_FLAVOR:
    • BUTTERFLY (Native iOS)
    • TREX (Native Android)
    • DET (Device Experience Tools)
    • IOS_NGP, ANDROID_NGP, IOS_NGC (Netflix Games Platform)

Microcontext-Only Flavors

Decision: The following ClientFlavor values will NOT exist in request-attrs UiFlavor:

  • BUTTERFLY (Native iOS)
  • TREX (Native Android)
  • DET (Device Experience Tools)
  • IOS_NGP (Netflix Games Platform SDK - iOS)
  • ANDROID_NGP (Netflix Games Platform SDK - Android)
  • IOS_NGC (Netflix Games Controller - iOS)

Rationale:

  1. request-attrs is being deprecated - Adding new values to a deprecated enum increases tech debt and extends its lifecycle unnecessarily.

  2. These clients are newer - They launched after microcontext became the standard. Services handling these clients should use microcontext directly.

  3. Encourages migration - Keeping these values microcontext-only creates a natural forcing function for teams to migrate to ClientFlavor.

  4. Minimal impact - Legacy code using UiFlavor will receive UNKNOWN_UI_FLAVOR for these clients, which is acceptable fallback behavior.

Conversion behavior:

ClientFlavor.BUTTERFLY   → UiFlavor.UNKNOWN_UI_FLAVOR
ClientFlavor.TREX        → UiFlavor.UNKNOWN_UI_FLAVOR
ClientFlavor.DET         → UiFlavor.UNKNOWN_UI_FLAVOR
ClientFlavor.IOS_NGP     → UiFlavor.UNKNOWN_UI_FLAVOR
ClientFlavor.ANDROID_NGP → UiFlavor.UNKNOWN_UI_FLAVOR
ClientFlavor.IOS_NGC     → UiFlavor.UNKNOWN_UI_FLAVOR

Any code that needs to handle these clients specifically must migrate to use ClientFlavor directly.

Affected Repositories

Based on Sourcegraph analysis, the following repositories import com.netflix.request.protogen.UiFlavor:

Repository Phase 4: Migration PR Phase 5: Cleanup PR
corp/algo
corp/dna-gusto
corp/ec-dynecom
corp/edge-api-next -
corp/edge-server PR #28694
corp/eveng-evidence-control-layer
corp/eveng-text-evidence
corp/gps-cgl
corp/gps-gps
corp/gps-gps-page-fallback
corp/gps-gps-page-router PR #1109
corp/gps-gpsintegrationtest
corp/gps-group-service-2
corp/recsys-sectiongenerator

Migration Strategy

Phase 1: Ensure Parity

Status: Complete

  1. Add WINDOWS_COUGAR to microcontext ClientFlavor enum (value 22, deprecated)
  2. Update ClientResolvers.clientCategory() to map WINDOWS_COUGARClientCategory.WIN
  3. Decision: Certain flavors (BUTTERFLY, TREX, DET, IOS_NGP, ANDROID_NGP, IOS_NGC) will remain microcontext-only (see Microcontext-Only Flavors)

Phase 2: Add Conversion Layer to request-attrs

Status: Complete — PR #618, PR #620

Goal: Allow request-attrs to use microcontext ClientFlavor as the source of truth

  1. Add microcontext dependency to request-attrs
  2. Create ClientFlavors utility class with bidirectional conversion
  3. Add withClientFlavor(ClientFlavor) to RequestAttributes.Builder
  4. Add getClientFlavor() to RequestAttributes
  5. Deprecate withUIFlavor(UiFlavor) and getUIFlavor()
  6. Add setClientFlavor() and getClientFlavor() to RequestAttributesProto
  7. Deprecate setUiFlavorEnum() and getUiFlavorEnum() in proto

Phase 3: Deprecate request-attrs UiFlavor

Status: Complete — PR #619, PR #624

  1. Mark UiFlavor proto enum as deprecated
  2. Mark RequestAttributes.getUIFlavor() as @Deprecated
  3. Add Javadoc pointing to ClientFlavor as replacement
  4. Update consumer documentation
  5. Remove BUTTERFLY, TREX, DET from UiFlavor (now microcontext-only)

Phase 4: Consumer Migration (Current)

For each affected repository, follow the Client Migration Guide:

  1. Upgrade to request-attrs 0.8.5 or later
  2. Update imports from UiFlavor to ClientFlavor
  3. Replace withUIFlavor() calls with withClientFlavor()
  4. Replace getUIFlavor() calls with getClientFlavor()
  5. Replace RequestAttributesProto.setUiFlavorEnum() with setClientFlavor()
  6. Replace RequestAttributesProto.getUiFlavorEnum() with getClientFlavor()
  7. Replace UiFlavor.name() / .toString() with ClientFlavors.toUiFlavorName()
  8. Update enum comparisons (UNKNOWN_UI_FLAVORUNSPECIFIED)
  9. Test thoroughly

Sample Prompt: implement the Client Migration as indicated in https://github.netflix.net/corp/dna-microcontext/blob/master/mkdocs/docs/migrations/flavor_migration.md in this codebase

Note: Consumers can migrate at their own pace. The request-attrs implementation uses ClientFlavor as internal storage with conversion at the API boundary, so old code using getUIFlavor() and new code using getClientFlavor() interoperate seamlessly:

// Internal storage: ClientFlavor clientFlavor

// New API (preferred) - direct access
withClientFlavor(ClientFlavor f) → clientFlavor = f
getClientFlavor()                → return clientFlavor

// Old API (deprecated) - converts at boundary
withUIFlavor(UiFlavor f)         → clientFlavor = ClientFlavors.fromUiFlavor(f)
getUIFlavor()                    → return ClientFlavors.toUiFlavor(clientFlavor)

Phase 5: Cleanup

  1. Remove deprecated UiFlavor from request-attrs
  2. Remove conversion utilities
  3. Archive migration documentation

Implementation Details

Microcontext Changes (This Repo)

Already Completed

  • WINDOWS_COUGAR added to flavor.proto at value 22 with [deprecated = true]
  • ClientResolvers.clientCategory() updated to handle WINDOWS_COUGARWIN

Required Changes

None additional in microcontext - the enum is now at parity.

request-attrs Changes

ClientFlavors Utility Class

Added ClientFlavors.java in request-attrs-api (com.netflix.request package) with:

  • fromName(String) - Parse ClientFlavor from string name (case-insensitive)
  • fromNameOrDefault(String) - Parse with fallback to UNSPECIFIED
  • fromUiFlavor(UiFlavor) - Convert UiFlavor → ClientFlavor
  • toUiFlavor(ClientFlavor) - Convert ClientFlavor → UiFlavor
  • toUiFlavorName(ClientFlavor) - Get the equivalent UiFlavor string name without importing UiFlavor

This utility is in the API module so it's available to all consumers without requiring additional dependencies.

RequestAttributes API Changes

New methods (preferred):

// Builder
Builder withClientFlavor(ClientFlavor flavor);

// Interface
default ClientFlavor getClientFlavor() {
    return ClientFlavor.UNSPECIFIED;
}

Deprecated methods:

// Builder
@Deprecated
Builder withUIFlavor(UiFlavor flavor);

// Interface
@Deprecated
default UiFlavor getUIFlavor() {
    return null;
}

Client Migration Guide

For consumers of request-attrs, follow these steps to migrate to ClientFlavor.

Prerequisites

Upgrade to request-attrs 0.8.1 or later:

Step 1: Update Imports

// Before
import com.netflix.request.protogen.UiFlavor;

// After
import netflix.context.client.flavor.ClientFlavor;

Step 2: Update Builder Calls

// Before
RequestAttributes attrs = builder
    .withUIFlavor(UiFlavor.AKIRA)
    .build();

// After
RequestAttributes attrs = builder
    .withClientFlavor(ClientFlavor.AKIRA)
    .build();

Step 3: Update Getter Calls

// Before
UiFlavor flavor = requestAttributes.getUIFlavor();
if (flavor == UiFlavor.AKIRA) { ... }

// After
ClientFlavor flavor = requestAttributes.getClientFlavor();
if (flavor == ClientFlavor.AKIRA) { ... }

Step 4: Update Proto Calls (Dual-Write Pattern)

If using RequestAttributesProto directly, follow the standard protobuf migration pattern:

During migration: Set both fields to ensure compatibility with all consumers:

// Transition period: Write BOTH fields
RequestAttributesProto.Builder builder = RequestAttributesProto.newBuilder();
builder.setClientFlavor(ClientFlavor.AKIRA);                    // New field (preferred)
builder.setUiFlavorEnum(ClientFlavors.toUiFlavor(ClientFlavor.AKIRA));  // Deprecated field (for compatibility)

// Reading: Prefer ClientFlavor, fall back to UiFlavor if needed
ClientFlavor flavor = proto.hasClientFlavor()
    ? proto.getClientFlavor()
    : ClientFlavors.fromUiFlavor(proto.getUiFlavorEnum());

Why dual-write? This follows the standard protobuf field migration pattern:

  1. Introduce new field (clientFlavor) — Complete
  2. Deprecate old field (uiFlavorEnum) — Complete
  3. Migrate clients to write both fieldsCurrent phase
  4. Once all producers write both fields, consumers can switch to reading only clientFlavor
  5. Once all consumers read only clientFlavor, producers can stop writing uiFlavorEnum
  6. Remove deprecated field — Future cleanup

After migration is complete (all libraries confirmed to consume clientFlavor):

// Final state: Only write ClientFlavor
RequestAttributesProto.Builder builder = RequestAttributesProto.newBuilder();
builder.setClientFlavor(ClientFlavor.AKIRA);

// Reading: Use ClientFlavor directly
ClientFlavor flavor = proto.getClientFlavor();

Note: Do not remove setUiFlavorEnum() calls until the migration team confirms all downstream consumers have been updated to read from clientFlavor. Premature removal will break services still reading from the deprecated field.

Step 5: Replace UiFlavor String Conversions

If your code converts UiFlavor to a string (e.g., .name() or .toString()), use toUiFlavorName() instead:

// Before - requires UiFlavor import
import com.netflix.request.protogen.UiFlavor;
String name = uiFlavor.name();

// After - no UiFlavor import needed
import com.netflix.request.ClientFlavors;
String name = ClientFlavors.toUiFlavorName(clientFlavor);

Step 6: Update Enum Comparisons

Old (UiFlavor) New (ClientFlavor)
UNKNOWN_UI_FLAVOR UNSPECIFIED
All other values Same name

Step 7: Handle Microcontext-Only Clients (if needed)

If your code needs to handle native mobile, DET, or Netflix Games Platform clients, you must use ClientFlavor:

ClientFlavor flavor = requestAttributes.getClientFlavor();
switch (flavor) {
    case BUTTERFLY:    // Native iOS
    case TREX:         // Native Android
    case DET:          // Device Experience Tools
    case IOS_NGP:      // Netflix Games Platform - iOS
    case ANDROID_NGP:  // Netflix Games Platform - Android
    case IOS_NGC:      // Netflix Games Controller - iOS
        // Handle these clients specifically
        break;
    // ...
}

These values do not exist in UiFlavor and will appear as UNKNOWN_UI_FLAVOR if using the deprecated API.

Step 8: Remove UiFlavor Dual-Write (After Migration Complete)

Prerequisites: Only perform this step after the migration team confirms:

  • All downstream consumers have been updated to read from clientFlavor
  • No services are still reading exclusively from uiFlavorEnum

Once confirmed, remove the deprecated uiFlavorEnum calls:

// Before (dual-write during transition)
RequestAttributesProto.Builder builder = RequestAttributesProto.newBuilder();
builder.setClientFlavor(ClientFlavor.AKIRA);
builder.setUiFlavorEnum(ClientFlavors.toUiFlavor(ClientFlavor.AKIRA));  // Remove this line

// After (final state)
RequestAttributesProto.Builder builder = RequestAttributesProto.newBuilder();
builder.setClientFlavor(ClientFlavor.AKIRA);

Also remove any remaining UiFlavor imports:

// Remove this import
import com.netflix.request.protogen.UiFlavor;

Checklist before removing dual-write:

  • Confirmed with migration team that all consumers read clientFlavor
  • Verified no alerts or errors related to missing uiFlavorEnum in downstream services
  • Tested in a non-production environment first

Using the Conversion Utility

During the transition, you can use ClientFlavors for conversion:

import com.netflix.request.ClientFlavors;

// Convert UiFlavor to ClientFlavor
ClientFlavor clientFlavor = ClientFlavors.fromUiFlavor(uiFlavor);

// Convert ClientFlavor to UiFlavor (for legacy code)
UiFlavor legacyFlavor = ClientFlavors.toUiFlavor(clientFlavor);

Getting UiFlavor String Without Importing UiFlavor

If your code only needs the UiFlavor string name (e.g., for logging, metrics, or passing to legacy APIs that accept strings), use toUiFlavorName() to avoid importing the deprecated UiFlavor enum entirely:

import com.netflix.request.ClientFlavors;
import netflix.context.client.flavor.ClientFlavor;

// Get the equivalent UiFlavor name as a String
String flavorName = ClientFlavors.toUiFlavorName(ClientFlavor.AKIRA);  // Returns "AKIRA"
String unknownName = ClientFlavors.toUiFlavorName(ClientFlavor.BUTTERFLY);  // Returns "UNKNOWN_UI_FLAVOR"
String nullSafe = ClientFlavors.toUiFlavorName(null);  // Returns "UNKNOWN_UI_FLAVOR"

This is the preferred approach when you need the string value, as it:

  1. Avoids importing UiFlavor - No dependency on the deprecated enum
  2. Handles microcontext-only flavors - Returns "UNKNOWN_UI_FLAVOR" for BUTTERFLY, TREX, DET, etc.
  3. Is null-safe - Returns "UNKNOWN_UI_FLAVOR" for null input
  4. Keeps mapping logic centralized - Uses the same conversion rules as toUiFlavor()

Common use cases:

// Logging
log.info("Processing request for flavor: {}", ClientFlavors.toUiFlavorName(clientFlavor));

// Metrics/dimensions
registry.counter("requests", "flavor", ClientFlavors.toUiFlavorName(clientFlavor));

// Legacy API calls that accept String
legacyService.setFlavor(ClientFlavors.toUiFlavorName(clientFlavor));

Risks and Mitigations

Risk Impact Mitigation
Breaking changes in consumers Medium Phased rollout with deprecation warnings
NGP flavors map to UNKNOWN in UiFlavor Low Intentional; forces migration for NGP-aware code
FAKIRA mapping Low Maps to AKIRA (web) when converting to UiFlavor

Status

Phase Status
Phase 1 (Parity) Complete
Phase 2 (Conversion) Complete (PR #618, PR #620)
Phase 3 (Deprecation) Complete (PR #619, PR #624)
Phase 4 (Consumer Migration) In Progress (see Affected Repositories)
Phase 5 (Cleanup) Not Started

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment