1.3 OSGi Configuration Management & Run Modes (.cfg.json)
Key Takeaways
- AEM as a Cloud Service uses Sling .cfg.json files for normal OSGi configurations; older formats are superseded, while multiline repoinit is a documented .config-format exception.
- Factory configurations require a tilde delimiter between the factory PID and a unique identifier in the filename (factoryPid~identifier.cfg.json).
- Run mode directories in ui.config follow strict precedence; compound directories like config.publish.prod override single run modes (config.publish) and base config, with the winning file replacing the configuration entirely without property merging.
- Environment-specific variables and secrets must be injected at runtime using $[env:ENV_NAME] and $[secret:SECRET_NAME] placeholders rather than hardcoding sensitive credentials in source control.
- OSGi R7 configuration interfaces utilize @ObjectClassDefinition and @AttributeDefinition to enforce type safety across primitive types, strings, and multi-value arrays.
1.3 OSGi Configuration Management & Run Modes (.cfg.json)
Quick Answer: AEM as a Cloud Service uses
.cfg.jsonfor normal OSGi configurations. Singleton services use<service.pid>.cfg.json, while factory configurations use<factory.pid>~<identifier>.cfg.json. Configurations reside in theui.configmodule under run mode folders (e.g.,config.publish.prod). Run modes resolve by exact replacement (no property merging). Secrets and dynamic environment variables are injected using$[secret:NAME]and$[env:NAME], while services define type-safe parameters using OSGi R7@ObjectClassDefinition.
The OSGi (Open Services Gateway initiative) framework forms the modular Java backbone of Adobe Experience Manager. At the heart of OSGi runtime management is the Configuration Admin service, which dynamically instantiates, configures, and binds OSGi services and components without requiring JVM restarts.
The Evolution of OSGi Configurations: .cfg.json
Historically, AEM developers used Apache Felix .config files or JCR sling:OsgiConfig nodes. For AEM as a Cloud Service, Adobe documents the Sling .cfg.json format for normal OSGi configuration files and describes the older forms as superseded. Repoinit is a deliberate exception: its multiline scripts are best authored in the .config format.
Why .cfg.json Is the Normal Cloud Service Format
- Standard JSON Syntax: Strictly adheres to standard JSON, eliminating parsing bugs common in proprietary
.configfiles. - First-Class Cloud Manager Integration: Cloud Manager code quality scanning validates
.cfg.jsonfiles during the build phase. - Immutable
/appsSupport: In AEM as a Cloud Service, code and configurations under/appsare packaged into an immutable Apache Sling Feature Model at build time. Direct runtime edits in the Felix Web Console (/system/console/configMgr) cannot persist across cloud container restarts.
Configuration PID Naming Rules: Singleton vs. Factory
Every OSGi configuration targets an OSGi component via its Persistent Identifier (PID), which typically matches the fully qualified Java class name of the component or its @ObjectClassDefinition interface.
1. Singleton Configurations
For an OSGi service where only a single instance exists across the entire JVM runtime, the filename matches the service PID followed by .cfg.json:
com.mybrand.core.services.impl.AnalyticsServiceImpl.cfg.json
Example file contents:
{
"service.endpoint": "https://analytics.mybrand.com/v2/events",
"connection.timeout.seconds": 15,
"enabled": true,
"tracking.tags": [
"production",
"global"
]
}
2. Factory Configurations
Certain OSGi services are designed to have multiple instances running concurrently with different parameters. Examples include logger configurations (org.apache.sling.commons.log.LogManager.factory.config) and replication agents. In .cfg.json, factory configurations require a tilde (~) delimiter followed by a unique, descriptive identifier:
<factory-pid>~<sub-service-identifier>.cfg.json
For example, to configure a custom logger for a project's core Java package:
org.apache.sling.commons.log.LogManager.factory.config~mybrand-core.cfg.json
{
"org.apache.sling.commons.log.names": [
"com.mybrand.core"
],
"org.apache.sling.commons.log.level": "DEBUG",
"org.apache.sling.commons.log.file": "logs/mybrand.log",
"org.apache.sling.commons.log.additiv": false
}
Exam Trap: Using a hyphen (
-) or underscore (_) instead of a tilde (~) to separate the factory PID from the identifier is a common mistake. Apache Sling will fail to recognize the file as a factory instance and will attempt to bind it as a broken singleton configuration.
Run Mode Directory Structure & Precedence in ui.config
In the standard Maven multi-module structure, OSGi configurations are normally maintained within the ui.config module under:
ui.config/src/main/content/jcr_root/apps/mybrand/osgiconfig/
AEM determines which configuration file to apply using run modes (such as author, publish, dev, stage, and prod). Run modes are mapped to subdirectories inside osgiconfig/.
Directory Hierarchy
config/: Default fallback applied to all instances and run modes.config.author/: Applied to all Author instances regardless of environment tier.config.publish/: Applied to all Publish instances regardless of environment tier.config.dev/,config.stage/,config.prod/: Applied to all instances within that specific environment tier.config.author.dev/: Applied specifically to the development Author instance.config.publish.prod/: Applied specifically to the production Publish instance.
Precedence Rules and the "No Merging" Principle
When AEM starts, the Sling settings service evaluates active run modes against directory names. The configuration resolution order follows two absolute rules that are heavily tested on the AD0-E128 exam:
- Specificity Wins: More specific compound run modes take precedence over single run modes, which take precedence over the base
config/directory: - Complete Replacement (No Property Merging): OSGi configurations in AEM do not merge properties across run modes. If a configuration for
com.mybrand.MyServiceexists inconfig/defining propertiesAandB, and another configuration for the same PID exists inconfig.publish.prod/defining only propertyB:- On a production publish instance, the file in
config.publish.prod/completely replaces the file inconfig/. - Property
Awill not be inherited fromconfig/—it will simply be missing (or fall back to the Java default declared in@AttributeDefinition).
- On a production publish instance, the file in
Environment Variables and Cloud Manager Secrets
Hardcoding API keys, database passwords, or environment-specific URLs into Git repositories violates enterprise security standards and Cloud Manager code quality rules. AEM as a Cloud Service solves this by allowing runtime value injection directly inside .cfg.json files.
Syntax for Variables and Secrets
| Type | Syntax Placeholder in .cfg.json | Purpose & Cloud Manager Handling |
|---|---|---|
| Environment Variable | "$[env:VAR_NAME;default=fallback]" | Non-sensitive, environment-specific values (e.g., target endpoints, timeouts). Set via Cloud Manager UI or API. Supports optional ;default= fallback. |
| Secret Variable | "$[secret:SECRET_NAME]" | Sensitive credentials (API keys, OAuth client secrets, passwords). Encrypted in Cloud Manager; write-only (cannot be retrieved once set). Does not support defaults. |
Practical Implementation Example
{
"payment.gateway.url": "$[env:PAYMENT_GATEWAY_URL;default=https://sandbox.payment.com/api]",
"payment.gateway.apiKey": "$[secret:PAYMENT_GATEWAY_API_KEY]",
"payment.timeout": 30,
"payment.sandbox": false
}
When deployed, Cloud Manager parses the placeholders at container startup and injects the corresponding environment variable or decrypted secret into the running OSGi service.
OSGi R7 Type-Safe Configuration Interfaces
In modern OSGi R7 Declarative Services, developers define configuration schemas using standard Java annotation interfaces rather than reading untyped dictionary properties.
package com.mybrand.core.config;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
@ObjectClassDefinition(
name = "MyBrand Payment Gateway Service Configuration",
description = "Configures API connection parameters for payment gateway integrations"
)
public @interface PaymentConfig {
@AttributeDefinition(
name = "Gateway Endpoint URL",
description = "Fully qualified HTTPS URL of the payment processor API",
type = AttributeType.STRING
)
String payment_gateway_url() default "https://sandbox.payment.com/api";
@AttributeDefinition(
name = "Gateway API Key",
description = "Secret key for API authorization",
type = AttributeType.PASSWORD
)
String payment_gateway_apiKey();
@AttributeDefinition(
name = "Connection Timeout (Seconds)",
type = AttributeType.INTEGER
)
int payment_timeout() default 30;
@AttributeDefinition(
name = "Supported Currencies",
type = AttributeType.STRING
)
String[] supported_currencies() default {"USD", "CAD", "EUR"};
@AttributeDefinition(
name = "Sandbox Mode",
type = AttributeType.BOOLEAN
)
boolean payment_sandbox() default false;
}
Consuming Configuration in a Declarative Service Component
The configuration interface is bound to an OSGi component using @Designate:
package com.mybrand.core.services.impl;
import com.mybrand.core.config.PaymentConfig;
import com.mybrand.core.services.PaymentService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(service = PaymentService.class, immediate = true)
@Designate(ocd = PaymentConfig.class)
public class PaymentServiceImpl implements PaymentService {
private static final Logger LOG = LoggerFactory.getLogger(PaymentServiceImpl.class);
private volatile PaymentConfig config;
@Activate
@Modified
protected void activate(PaymentConfig config) {
this.config = config;
LOG.info("PaymentService active: endpoint={}, timeout={}s, sandbox={}",
config.payment_gateway_url(),
config.payment_timeout(),
config.payment_sandbox());
}
@Override
public boolean isSandboxEnabled() {
return config.payment_sandbox();
}
}
By leveraging @ObjectClassDefinition, parameters are strictly type-checked at compile and runtime. Values defined in .cfg.json files automatically map to primitive types (int, boolean), strings, passwords, and multi-value arrays (String[]), eliminating manual type casting and null-pointer hazards.
A developer is creating a factory configuration in the ui.config module for the Apache Sling LogManager service (org.apache.sling.commons.log.LogManager.factory.config) to define a custom project logger. What is the correct file naming convention under apps/myproject/osgiconfig/config/?
An AEM project defines com.example.service.AppConfig.cfg.json in both config/ (defining properties enabled=true and timeout=30) and config.publish.prod/ (defining only timeout=60). When deployed to a production Publish instance, what are the resolved configuration values for AppConfig?
An AEM application needs to connect to an external payment gateway that requires an API key and a service endpoint URL. How should these sensitive and environment-specific values be configured in ui.config for AEM as a Cloud Service?
A developer attempts to modify an OSGi configuration at runtime on an AEM as a Cloud Service production author instance by saving changes in the Apache Felix Web Console (/system/console/configMgr). What is the outcome of this action?