Real-World Scenarios: Flipping a Feature Toggle's Default Changed More Pages Than Expected
A one-line change to an inherited page property's default value quietly changed behavior on hundreds of pages that had never explicitly set it — how it was traced, and the audit query that should have run before the change shipped.
Background reading: Inherited Page Properties and Site-Wide Feature Toggles covers the mechanics this post assumes.
Problem Statement
A marketing requirement said analytics cookies should be enabled by default across most of the site, with only a small, known set of legacy pages needing them left off. The change that shipped looked trivially small: one boolean literal, in one Java file, changed from false to true — the hardcoded default passed to getInherited(ANALYTICS_COOKIES_ENABLED, false) became getInherited(ANALYTICS_COOKIES_ENABLED, true).
Within a day, compliance flagged that analytics cookies were now firing on pages that were never supposed to have them — a handful of region-specific pages under strict local privacy requirements. Those pages had never explicitly set the property either way; they had simply relied on the old site-wide default of false, the same as almost every other page on the site. Nobody had a list of which pages fell into that category, because "relying on the default" isn't something that shows up anywhere in content — it's the absence of a property, indistinguishable in the repository from a page that simply never needed to think about the setting at all.
Approach and Why
The fix needed to happen in two places, because the actual bug was a missing step in the rollout process, not a bug in the inheritance code itself:
- Immediate remediation: explicitly set
analyticsCookiesEnabledtofalseat the section roots for the affected region-specific pages, so they no longer depend on (and are no longer affected by) the site-wide default at all — regardless of what that default is ever changed to in the future. - Process fix: before shipping the toggle's default change, there should have been an audit identifying every page in the tree that had never explicitly set the property, cross-checked against known compliance-sensitive sections. This needed to become a repeatable script, not a one-off manual check, since defaults on inherited toggles get revisited periodically.
Explicitly setting the property on the affected pages, rather than reverting the default back to false globally, was the deliberate choice — reverting would have undone the actual goal (most of the site should default to enabled) just to patch the pages that needed an exception. The correct fix scopes the exception to where it belongs.
POC
class InheritedTogglePreflightTest {
@Test
void defaultChangeIsAuditedAgainstPagesRelyingOnIt() throws Exception {
List<PageCandidate> allPages = pageTreeScanner.scanAll("/content/mysite");
List<PageCandidate> pagesRelyingOnDefault = allPages.stream()
.filter(page -> !page.hasExplicitProperty("analyticsCookiesEnabled"))
.collect(Collectors.toList());
List<PageCandidate> complianceSensitivePages = pagesRelyingOnDefault.stream()
.filter(page -> page.getPath().matches(".*/(eu|region-[a-z]{2})/.*"))
.collect(Collectors.toList());
assertThat(complianceSensitivePages)
.as("compliance-sensitive pages must never rely on an inherited toggle's default - "
+ "they should set the property explicitly regardless of what the default is")
.isEmpty();
}
@Test
void explicitFalseIsPreservedRegardlessOfDefaultChange() {
Resource pageWithExplicitFalse = context.create().resource(
"/content/mysite/eu/privacy-page/jcr:content",
Map.of("analyticsCookiesEnabled", false));
InheritanceValueMap inherited = new HierarchyNodeInheritanceValueMap(pageWithExplicitFalse);
// Regardless of what default is passed here, an explicit false on this
// exact resource always wins - this assertion should hold both before
// and after any change to the hardcoded default elsewhere in the code.
assertThat(inherited.getInherited("analyticsCookiesEnabled", true)).isFalse();
}
}
The first test is the preflight check that should run before any hardcoded default changes — it fails loudly, listing exactly which compliance-sensitive pages are exposed, rather than discovering the exposure after the fact via a compliance complaint.
Implementation
Remediation — explicit property set at the affected section roots, rather than relying on any future default:
<!-- /content/mysite/eu/jcr:content -->
<jcr:content
analyticsCookiesEnabled="{Boolean}false"
... />
Audit tooling — a scanner used both for the incident remediation and wired into the preflight test above:
final class PageTreeScanner {
private final ResourceResolver resourceResolver;
PageTreeScanner(ResourceResolver resourceResolver) {
this.resourceResolver = resourceResolver;
}
List<PageCandidate> scanAll(String rootPath) {
Resource root = resourceResolver.getResource(rootPath);
List<PageCandidate> results = new ArrayList<>();
if (root != null) {
collect(root, results);
}
return results;
}
private void collect(Resource resource, List<PageCandidate> results) {
Resource contentResource = resource.getChild(JcrConstants.JCR_CONTENT);
if (contentResource != null) {
results.add(new PageCandidate(resource.getPath(), contentResource.getValueMap()));
}
for (Resource child : resource.getChildren()) {
collect(child, results);
}
}
}
final class PageCandidate {
private final String path;
private final ValueMap ownProperties;
PageCandidate(String path, ValueMap ownProperties) {
this.path = path;
this.ownProperties = ownProperties;
}
String getPath() {
return path;
}
boolean hasExplicitProperty(String name) {
return ownProperties.containsKey(name);
}
}
The key distinction this scanner makes deliberately: ownProperties.containsKey(name) checks only the page's own jcr:content, never walking up — the opposite of HierarchyNodeInheritanceValueMap, and exactly the check needed to answer "does this specific page rely on the default" rather than "what value does this page effectively have."
Rollout Steps
- Ran the audit scanner against production content, identified 6 compliance-sensitive section roots relying on the default, set
analyticsCookiesEnabled="{Boolean}false"explicitly on each. - Verified via the browser's network tab on a sample of those pages that analytics cookie scripts no longer fired after the fix, while confirming the rest of the site correctly picked up the new
truedefault. - Added the preflight test to the CI suite for the page model module, scoped to run specifically when a pull request touches the file containing inherited-property defaults, giving future default changes an automatic check against the current content tree.
- Documented the six explicitly-configured section roots in the team's site-configuration reference, so they're not mistaken for stale/dead configuration by someone doing an unrelated content review later.
Why This Approach Held Up
The remediation made the previously-implicit exception explicit in content, which means the six affected pages are now immune to any future default change, not just this one — nobody has to remember they were an edge case. The preflight test encodes the actual lesson (compliance-sensitive pages must never depend on an inherited default) as a repeatable, automated check rather than a note in a postmortem document that only helps if someone remembers to reread it before the next toggle change.
Enjoyed this chapter?
Get an email when I publish the next chapter. No spam — just new technical deep-dives.
Comments
Share feedback or questions about this blog post.
No comments yet. Be the first to share your thoughts.