@@ -398,6 +398,20 @@ DecisionResponse<FeatureDecision> getVariationFromExperiment(@Nonnull ProjectCon
398398 for (String experimentId : featureFlag .getExperimentIds ()) {
399399 Experiment experiment = projectConfig .getExperimentIdMapping ().get (experimentId );
400400
401+ // Check local holdouts targeting this experiment rule before regular evaluation (FSSDK-12369)
402+ if (experiment != null ) {
403+ List <Holdout > localHoldouts = projectConfig .getHoldoutConfig ().getHoldoutsForRule (experiment .getId ());
404+ for (Holdout holdout : localHoldouts ) {
405+ DecisionResponse <Variation > holdoutDecision = getVariationForHoldout (holdout , user , projectConfig );
406+ reasons .merge (holdoutDecision .getReasons ());
407+ if (holdoutDecision .getResult () != null ) {
408+ return new DecisionResponse <>(
409+ new FeatureDecision (holdout , holdoutDecision .getResult (), FeatureDecision .DecisionSource .HOLDOUT ),
410+ reasons );
411+ }
412+ }
413+ }
414+
401415 DecisionResponse <Variation > decisionVariation =
402416 getVariationFromExperimentRule (projectConfig , featureFlag .getKey (), experiment , user , options , userProfileTracker , decisionPath );
403417 reasons .merge (decisionVariation .getReasons ());
@@ -469,6 +483,18 @@ DecisionResponse<FeatureDecision> getVariationForFeatureInRollout(@Nonnull Featu
469483 while (index < rolloutRulesLength ) {
470484 Experiment rule = rollout .getExperiments ().get (index );
471485
486+ // Check local holdouts targeting this delivery rule before regular evaluation (FSSDK-12369)
487+ List <Holdout > localHoldoutsForRule = projectConfig .getHoldoutConfig ().getHoldoutsForRule (rule .getId ());
488+ boolean localHoldoutHit = false ;
489+ for (Holdout holdout : localHoldoutsForRule ) {
490+ DecisionResponse <Variation > holdoutDecision = getVariationForHoldout (holdout , user , projectConfig );
491+ reasons .merge (holdoutDecision .getReasons ());
492+ if (holdoutDecision .getResult () != null ) {
493+ FeatureDecision holdoutFeatureDecision = new FeatureDecision (holdout , holdoutDecision .getResult (), FeatureDecision .DecisionSource .HOLDOUT );
494+ return new DecisionResponse (holdoutFeatureDecision , reasons );
495+ }
496+ }
497+
472498 DecisionResponse <AbstractMap .SimpleEntry > decisionVariationResponse = getVariationFromDeliveryRule (
473499 projectConfig ,
474500 featureFlag .getKey (),
@@ -846,16 +872,6 @@ private DecisionResponse<Variation> getVariationFromExperimentRule(@Nonnull Proj
846872 return new DecisionResponse (variation , reasons );
847873 }
848874
849- // Check local holdouts targeting this specific experiment rule (FSSDK-12369)
850- List <Holdout > localHoldouts = projectConfig .getHoldoutConfig ().getHoldoutsForRule (rule .getId ());
851- for (Holdout holdout : localHoldouts ) {
852- DecisionResponse <Variation > holdoutDecision = getVariationForHoldout (holdout , user , projectConfig );
853- reasons .merge (holdoutDecision .getReasons ());
854- if (holdoutDecision .getResult () != null ) {
855- return new DecisionResponse <>(holdoutDecision .getResult (), reasons );
856- }
857- }
858-
859875 //regular decision
860876 DecisionResponse <Variation > decisionResponse = getVariation (rule , user , projectConfig , options , userProfileTracker , null , decisionPath );
861877 reasons .merge (decisionResponse .getReasons ());
@@ -906,17 +922,6 @@ DecisionResponse<AbstractMap.SimpleEntry> getVariationFromDeliveryRule(@Nonnull
906922 return new DecisionResponse (variationToSkipToEveryoneElsePair , reasons );
907923 }
908924
909- // Check local holdouts targeting this specific delivery rule (FSSDK-12369)
910- List <Holdout > localHoldouts = projectConfig .getHoldoutConfig ().getHoldoutsForRule (rule .getId ());
911- for (Holdout holdout : localHoldouts ) {
912- DecisionResponse <Variation > holdoutDecision = getVariationForHoldout (holdout , user , projectConfig );
913- reasons .merge (holdoutDecision .getReasons ());
914- if (holdoutDecision .getResult () != null ) {
915- variationToSkipToEveryoneElsePair = new AbstractMap .SimpleEntry <>(holdoutDecision .getResult (), false );
916- return new DecisionResponse (variationToSkipToEveryoneElsePair , reasons );
917- }
918- }
919-
920925 // Handle a regular decision
921926 String bucketingId = getBucketingId (user .getUserId (), user .getAttributes ());
922927 Boolean everyoneElse = (ruleIndex == rules .size () - 1 );
0 commit comments