Skip to content

fix: ConfigService.getConfig(appId, namespace) returns wrong app's config - #140

Merged
mergify[bot] merged 3 commits into
apolloconfig:mainfrom
Shawyeok:fix/appid-leaked-in-properties-compatible-config
Jun 21, 2026
Merged

fix: ConfigService.getConfig(appId, namespace) returns wrong app's config#140
mergify[bot] merged 3 commits into
apolloconfig:mainfrom
Shawyeok:fix/appid-leaked-in-properties-compatible-config

Conversation

@Shawyeok

@ShawyeokShawyeok commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Problem

ConfigService.getConfig(appId, "some-namespace.yml") silently returns config belonging to the default application (app.id in app.properties) instead of the requested appId, with no error or warning.

The same bug affects any non-properties namespace format (YAML, YML, JSON, XML, TXT).

Root cause

DefaultConfigFactory.create(appId, namespace) correctly dispatches to createPropertiesCompatibleFileConfigRepository(appId, namespace, format) for non-properties namespaces, but that method then calls the two-argConfigService.getConfigFile(namespace, format) — silently dropping appId:

// DefaultConfigFactory.java (before fix)PropertiesCompatibleFileConfigRepositorycreatePropertiesCompatibleFileConfigRepository(
StringappId, Stringnamespace, ConfigFileFormatformat) {
StringactualNamespaceName = trimNamespaceFormat(namespace, format);
PropertiesCompatibleConfigFileconfigFile = (PropertiesCompatibleConfigFile) ConfigService
.getConfigFile(actualNamespaceName, format); // ← appId silently droppedreturnnewPropertiesCompatibleFileConfigRepository(configFile);
}

ConfigManager already had a correct three-arg getConfigFile(appId, namespace, format) overload, but it was not exposed on ConfigService.

Fix

ConfigService.java — add the missing overload that mirrors the existing getConfig(appId, namespace) pattern:

publicstaticConfigFilegetConfigFile(StringappId, Stringnamespace,
ConfigFileFormatconfigFileFormat) {
returns_instance.getManager().getConfigFile(appId, namespace, configFileFormat);
}

DefaultConfigFactory.java — use the new overload so appId is preserved:

configFile = (PropertiesCompatibleConfigFile) ConfigService
.getConfigFile(appId, actualNamespaceName, format); // ← appId preserved

Tests

| Test | What it verifies |
|------|-----------------|| ConfigServiceTest.testGetConfigWithCustomAppId | ConfigService.getConfig(appId, namespace) returns a Config whose property values reflect the requested appId, not the default |
| ConfigServiceTest.testGetConfigFileWithCustomAppId | ConfigService.getConfigFile(appId, ns, format) returns a ConfigFile whose getAppId() matches the requested appId |

Affected versions

Reproducible on apollo-client 2.4.0 and 2.5.0. ConfigFileFormat.Properties namespaces are unaffected (they go through createConfigRepository, not createPropertiesCompatibleFileConfigRepository).

Summary by CodeRabbit

  • New Features

    • Added a public overload to retrieve configuration files using a custom application ID, namespace, and an explicit format for finer-grained configuration access.
  • Tests

    • Expanded test coverage to verify custom application IDs are correctly propagated for both configuration retrieval and configuration-file retrieval, including behavior when resolving properties-compatible namespaces.

@coderabbitai

coderabbitaiBot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces a new public ConfigService.getConfigFile(appId, namespace, configFileFormat) overload to enable configuration-file lookup with a custom application ID. The factory implementation is updated to pass appId when retrieving config files, and three test methods with a mock helper class validate that the new overload correctly handles custom appId scenarios for both Config and ConfigFile instances.

Changes

AppId-aware ConfigFile retrieval

Layer / File(s)Summary
ConfigService getConfigFile overload with appId
apollo-client/src/main/java/com/ctrip/framework/apollo/ConfigService.java
Adds a public static method accepting appId, namespace, and configFileFormat parameters, delegating to the underlying ConfigManager for retrieval.
Factory wiring and test validation
apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java, apollo-client/src/test/java/com/ctrip/framework/apollo/ConfigServiceTest.java
DefaultConfigFactory updates the call to ConfigService.getConfigFile to include appId. Three new test methods verify that ConfigService overloads accepting a custom appId correctly propagate the appId through both direct ConfigFactory mocking and internal DefaultConfigFactory transformation. A new MockPropertiesCompatibleConfigFile class supports testing by returning appId as an observable property.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • apolloconfig/apollo-java#70: Multi-appId plumbing to make config-file retrieval appId-aware, aligning with this PR's addition of the ConfigService.getConfigFile(appId, namespace, format) overload.

Suggested reviewers

  • nobodyiam

Poem

🐰 A rabbit hops through configuration with glee,
With appId now guiding each lookup, you see!
The factory calls out with the ID in hand,
Tests validate paths across namespaced land.
One simple new method makes all of it clear! 🎯

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 4.17% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main fix: adding proper appId handling to ConfigService.getConfig() to prevent it from returning the wrong application's config.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecovBot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.94%. Comparing base (d4b76f8) to head (8f66192).
⚠️ Report is 29 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #140 +/- ##
============================================
+ Coverage 68.68% 70.94% +2.26% - Complexity 1503 1635 +132 
============================================
Files 212 224 +12 Lines 6396 6733 +337 Branches 647 680 +33 ============================================
+ Hits 4393 4777 +384 + Misses 1673 1603 -70 - Partials 330 353 +23 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apollo-client/src/main/java/com/ctrip/framework/apollo/ConfigService.java`:
- Around line 104-115: Update the Javadoc for ConfigService.getConfigFile:
clarify that the namespace parameter should NOT include a file extension (e.g.,
use "application" not "application.yml") because the extension is derived from
the configFileFormat and appended internally by DefaultConfigManager; reference
ConfigService.getConfigFile, the namespace parameter, and ConfigFileFormat (and
DefaultConfigManager behavior) in the comment so callers know to pass just the
base namespace name.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: af07b96b-693d-4836-af0e-931a7384e731

📥 Commits

Reviewing files that changed from the base of the PR and between a9a6edc and 2a1096e.

📒 Files selected for processing (3)
  • apollo-client/src/main/java/com/ctrip/framework/apollo/ConfigService.java
  • apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java
  • apollo-client/src/test/java/com/ctrip/framework/apollo/ConfigServiceTest.java

@Shawyeok
Shawyeokforce-pushed the fix/appid-leaked-in-properties-compatible-config branch from 2a1096e to c7d2dacCompareJune 9, 2026 10:05

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apollo-compat-tests/apollo-spring-compat-it/src/test/java/com/ctrip/framework/apollo/compat/spring/SpringApolloEventListenerProbe.java (1)

41-55: Discarded namespaces aren’t a reliability problem in the current test
waitForNamespace(...) consumes the namespaces queue via poll(...) and discards non-matching values, but in this module the SpringApolloEventListenerProbe is only consumed once (waitForNamespace("application", ...) in SpringAnnotationCompatibilityTest), and there are no other usages of the probe (no other waitForNamespace/pollNamespace calls). Discarding won’t affect later assertions here.
If you later reuse the same probe instance to wait for multiple different namespaces, consider a non-destructive approach (e.g., retain unmatched values).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@apollo-compat-tests/apollo-spring-compat-it/src/test/java/com/ctrip/framework/apollo/compat/spring/SpringApolloEventListenerProbe.java`
around lines 41 - 55, waitForNamespace in SpringApolloEventListenerProbe
currently polls and discards non-matching entries from the namespaces queue,
which may lose events if the probe is reused; change the logic in
waitForNamespace (and any helper like namespaces queue handling) to be
non-destructive: peek the head and only poll when it matches expectedNamespace
(or temporarily buffer unmatched values and requeue them if needed) so unmatched
namespace values are retained for subsequent waits (reference waitForNamespace,
namespaces, and SpringApolloEventListenerProbe to locate the code; consider
SpringAnnotationCompatibilityTest which calls waitForNamespace("application",
...)).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@apollo-compat-tests/apollo-spring-compat-it/src/test/java/com/ctrip/framework/apollo/compat/spring/SpringApolloEventListenerProbe.java`:
- Around line 41-55: waitForNamespace in SpringApolloEventListenerProbe
currently polls and discards non-matching entries from the namespaces queue,
which may lose events if the probe is reused; change the logic in
waitForNamespace (and any helper like namespaces queue handling) to be
non-destructive: peek the head and only poll when it matches expectedNamespace
(or temporarily buffer unmatched values and requeue them if needed) so unmatched
namespace values are retained for subsequent waits (reference waitForNamespace,
namespaces, and SpringApolloEventListenerProbe to locate the code; consider
SpringAnnotationCompatibilityTest which calls waitForNamespace("application",
...)).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 85763920-8d5b-434f-9070-533887030288

📥 Commits

Reviewing files that changed from the base of the PR and between c7d2dac and 6261c1e.

📒 Files selected for processing (2)
  • apollo-compat-tests/apollo-spring-compat-it/src/test/java/com/ctrip/framework/apollo/compat/spring/SpringAnnotationCompatibilityTest.java
  • apollo-compat-tests/apollo-spring-compat-it/src/test/java/com/ctrip/framework/apollo/compat/spring/SpringApolloEventListenerProbe.java

@nobodyiam

nobodyiam commented Jun 10, 2026

Copy link
Copy Markdown
Member

Thanks for the fix. I checked the latest head (f6110d1faffe7e382778bbfc8c15156527694e05): the non-properties path now keeps the custom appId by calling ConfigService.getConfigFile(appId, actualNamespaceName, format), and the new overload preserves the existing two-argument API. The added tests cover both custom-app config and config-file lookup, and the namespace Javadoc example now correctly omits the file extension.

I do not see a blocking issue from this pass. A maintainer can do the final approval/merge decision.

Note: this reply was generated and posted automatically by AI for initial triage; a maintainer will follow up if needed.

@Shawyeok

Copy link
Copy Markdown
ContributorAuthor

Thanks for the fix. I checked the latest head (f6110d1faffe7e382778bbfc8c15156527694e05): the non-properties path now keeps the custom appId by calling ConfigService.getConfigFile(appId, actualNamespaceName, format), and the new overload preserves the existing two-argument API. The added tests cover both custom-app config and config-file lookup, and the namespace Javadoc example now correctly omits the file extension.

I do not see a blocking issue from this pass. A maintainer can do the final approval/merge decision.

Note: this reply was generated and posted automatically by AI for initial triage; a maintainer will follow up if needed.

@nobodyiam Do you time to take a look about this issue?

…tory for non-default appId
DefaultConfigFactory.createPropertiesCompatibleFileConfigRepository() received an appId
parameter but called ConfigService.getConfigFile(namespace, format) — the two-arg overload
that ignores appId and resolves against the default app.id from app.properties.
Fixes the bug by:
1. Adding ConfigService.getConfigFile(appId, namespace, format) that delegates to the
already-correct ConfigManager.getConfigFile(appId, namespace, format).
2. Updating DefaultConfigFactory to call the new three-arg overload so the caller-specified
appId is preserved.
Adds tests:
- DefaultConfigFactoryTest.testCreatePropertiesCompatibleFileConfigRepositoryForwardsCustomAppId:
verifies ConfigManager is invoked with the supplied appId, never the default.
- ConfigServiceTest.testGetConfigFileWithCustomAppId:
verifies the new ConfigService.getConfigFile(appId, ns, format) overload returns a
ConfigFile whose getAppId() equals the requested appId.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nobodyiam
nobodyiamforce-pushed the fix/appid-leaked-in-properties-compatible-config branch from f6110d1 to 81cab3cCompareJune 13, 2026 01:52

@nobodyiamnobodyiam left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the fix. The implementation direction looks right, but please add one regression test for the actual non-properties path before approval.

ConfigServiceTest.testGetConfigWithCustomAppId currently uses a properties namespace ("mock"), and testGetConfigFileWithCustomAppId calls the new getConfigFile(appId, namespace, format) overload directly. Please add a test for ConfigService.getConfig(customAppId, "mock.yml") or an equivalent DefaultConfigFactory path, so the test would fail if DefaultConfigFactory.createPropertiesCompatibleFileConfigRepository(...) drops the custom appId again.

@Shawyeok

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review @nobodyiam. Added ConfigServiceTest.testGetConfigWithCustomAppIdForPropertiesCompatibleNamespace, which drives the real DefaultConfigFactory path via ConfigService.getConfig(customAppId, "mock.yml") (create → createPropertiesCompatibleFileConfigRepositorygetConfigFile(appId, namespace, format)). It stubs only createConfigFile and echoes the received appId into the resulting Config, so it fails if createPropertiesCompatibleFileConfigRepository(...) drops the custom appId again. Verified it fails on the pre-fix code (expected:<customAppId> but was:<someAppId>) and passes with the fix.

…amespace
Add ConfigServiceTest.testGetConfigWithCustomAppIdForPropertiesCompatibleNamespace,
which drives the real DefaultConfigFactory path
(create -> createPropertiesCompatibleFileConfigRepository ->
ConfigService.getConfigFile(appId, namespace, format)) for a .yml namespace.
The existing custom-appId tests either used a properties namespace or called the
new getConfigFile overload directly, so neither would catch
DefaultConfigFactory.createPropertiesCompatibleFileConfigRepository dropping the
custom appId again. The new test stubs only createConfigFile and echoes the
received appId into the resulting Config, so it fails if the appId is dropped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Shawyeok
Shawyeokforce-pushed the fix/appid-leaked-in-properties-compatible-config branch from f8722b2 to 25b8bdeCompareJune 21, 2026 03:30

@nobodyiamnobodyiam left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the update. The new regression test covers the requested non-properties path by calling ConfigService.getConfig(customAppId, "mock.yml") and exercising the real DefaultConfigFactory path while only stubbing createConfigFile.

The current checks are green, and I do not see any remaining blocking issues from this pass.

@mergify

mergifyBot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Queued — the merge queue status continues in this comment ↓.

@mergify

mergifyBot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-06-21 07:12 UTC · Rule: multi-commit
  • Checks passed · in-place
  • Merged2026-06-21 07:16 UTC · at 8f66192a3ca571ce3f245dfdce1ff4cdbe48a4ee · squash

This pull request spent 3 minutes 18 seconds in the queue, including 3 minutes 3 seconds running CI.

Required conditions to merge
  • #approved-reviews-by >= 1
  • #approved-reviews-by >= 1 [🛡 GitHub branch protection]
  • #changes-requested-reviews-by = 0
  • #commits > 1
  • -closed
  • -conflict
  • -draft
  • base = main
  • check-success = CLAssistant
  • check-success = compat-api
  • check-success = compat-spring-boot-spring-boot-2.7-jdk8
  • check-success = compat-spring-boot-spring-boot-3.3-jdk17
  • check-success = compat-spring-boot-spring-boot-4.0-jdk17
  • check-success = compat-spring-spring-3.1.1-jdk8
  • check-success = compat-spring-spring-6.1-jdk17
  • check-success = compile-matrix (11)
  • check-success = compile-matrix (17)
  • check-success = compile-matrix (8)
  • check-success = license
  • check-success = unit-integration-pr
  • github-review-decision = APPROVED [🛡 GitHub branch protection]
  • any of [🛡 GitHub branch protection]:
    • check-success = license
    • check-neutral = license
    • check-skipped = license
  • any of [🛡 GitHub branch protection]:
    • check-success = CLAssistant
    • check-neutral = CLAssistant
    • check-skipped = CLAssistant
  • any of [🛡 GitHub branch protection]:
    • check-success = compat-api
    • check-neutral = compat-api
    • check-skipped = compat-api
  • any of [🛡 GitHub branch protection]:
    • check-success = compat-spring-boot-spring-boot-2.7-jdk8
    • check-neutral = compat-spring-boot-spring-boot-2.7-jdk8
    • check-skipped = compat-spring-boot-spring-boot-2.7-jdk8
  • any of [🛡 GitHub branch protection]:
    • check-success = compat-spring-boot-spring-boot-3.3-jdk17
    • check-neutral = compat-spring-boot-spring-boot-3.3-jdk17
    • check-skipped = compat-spring-boot-spring-boot-3.3-jdk17
  • any of [🛡 GitHub branch protection]:
    • check-success = compat-spring-boot-spring-boot-4.0-jdk17
    • check-neutral = compat-spring-boot-spring-boot-4.0-jdk17
    • check-skipped = compat-spring-boot-spring-boot-4.0-jdk17
  • any of [🛡 GitHub branch protection]:
    • check-success = compat-spring-spring-3.1.1-jdk8
    • check-neutral = compat-spring-spring-3.1.1-jdk8
    • check-skipped = compat-spring-spring-3.1.1-jdk8
  • any of [🛡 GitHub branch protection]:
    • check-success = compat-spring-spring-6.1-jdk17
    • check-neutral = compat-spring-spring-6.1-jdk17
    • check-skipped = compat-spring-spring-6.1-jdk17
  • any of [🛡 GitHub branch protection]:
    • check-success = compile-matrix (17)
    • check-neutral = compile-matrix (17)
    • check-skipped = compile-matrix (17)
  • any of [🛡 GitHub branch protection]:
    • check-success = compile-matrix (11)
    • check-neutral = compile-matrix (11)
    • check-skipped = compile-matrix (11)
  • any of [🛡 GitHub branch protection]:
    • check-success = compile-matrix (8)
    • check-neutral = compile-matrix (8)
    • check-skipped = compile-matrix (8)
  • any of [🛡 GitHub branch protection]:
    • check-success = unit-integration-pr
    • check-neutral = unit-integration-pr
    • check-skipped = unit-integration-pr

@mergifymergifyBot added the queued label Jun 21, 2026
@mergify
mergifyBot merged commit 023217c into apolloconfig:mainJun 21, 2026
18 checks passed
@mergifymergifyBot removed the queued label Jun 21, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 21, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@Shawyeok@nobodyiam