Skip to main content

OSGi Configuration: Files, Run Modes and Environment Variables

Series: AEM Backend Essentials — Part 3

Updated 6 min readMarkdown
Part of seriesAEM Backend Essentials
OSGi Configuration: Files, Run Modes and Environment Variables

Almost every service has settings that change between environments: an endpoint URL, a feature toggle, a timeout, a credential. OSGi lets you keep those out of your code and supply them as configuration. On AEM as a Cloud Service, configuration is part of your codebase and arrives through the pipeline, which is a real change from 6.5, where many teams edited it by hand on the server. This post shows how to define, place and vary configuration safely.

Step 1: Describe the Configuration in Code

Use the OSGi metatype annotations to define what can be configured:

@ObjectClassDefinition(name = "My Project - Catalog API",
        description = "Settings for the external catalog API")
public @interface CatalogApiConfig {

    @AttributeDefinition(name = "Base URL")
    String baseUrl() default "https://catalog.example.com/api";

    @AttributeDefinition(name = "Enabled")
    boolean enabled() default true;

    @AttributeDefinition(name = "API key")
    String apiKey() default "";

    @AttributeDefinition(name = "Read timeout (ms)")
    int readTimeoutMs() default 5000;
}

Attach it to the component and read it in @Activate and @Modified:

@Component(service = CatalogClient.class)
@Designate(ocd = CatalogApiConfig.class)
public class CatalogClientImpl implements CatalogClient {

    private volatile CatalogApiConfig config;

    @Activate
    @Modified
    protected void activate(CatalogApiConfig config) {
        this.config = config;
    }
}

Putting @Activate and @Modified on the same method keeps the two paths identical. The defaults in the interface are used whenever a value isn't supplied.

Step 2: Supply Values with a Configuration File

A configuration file is named after the component's PID, which by default is its fully qualified class name:

com.myproject.core.services.CatalogClientImpl.cfg.json

{
  "baseUrl": "https://catalog-dev.example.com/api",
  "enabled": true,
  "readTimeoutMs": 5000
}

In a Cloud Service project, these files live in the ui.config module under:

ui.config/src/main/content/jcr_root/apps/myproject/osgiconfig/
├── config/                  ← applies everywhere
├── config.author/           ← Author only
├── config.publish/          ← Publish only
├── config.dev/              ← Dev environments
├── config.stage/            ← Stage
├── config.prod/             ← Production
└── config.publish.prod/     ← Publish on Production

The folder name is a run mode selector. AEM applies the folders that match the instance, so you can set a different baseUrl per environment while keeping everything else in config/.

Some things to know about run modes on Cloud Service:

  • You use the service-type run modes author and publish, and the environment-type run modes dev, stage and prod, alone or combined (for example config.author.dev)
  • You can't invent your own run modes as you could on 6.5
  • More specific folders override values from more general ones

Step 3: Use Environment Variables and Secrets

Don't commit credentials or environment-specific hostnames to Git. Cloud Service supports placeholders in configuration files that are filled from Cloud Manager environment variables:

{
  "baseUrl": "$[env:CATALOG_BASE_URL;default=https://catalog-dev.example.com/api]",
  "apiKey": "$[secret:CATALOG_API_KEY]",
  "readTimeoutMs": 5000
}
  • $[env:NAME] reads a normal environment variable. You can give a default for local development
  • $[secret:NAME] reads a secret variable, which is stored more carefully and not shown after you set it
  • You set the values in Cloud Manager per environment, through the UI, API or CLI

This keeps one file in Git while Dev, Stage and Production each get their own values. Check Adobe's current documentation for where secret placeholders are permitted and for size and naming limits.

Step 4: Factory Configurations

When you need several configurations of the same kind (one per site, one per API), use a factory configuration. Its file name includes a tilde and an instance name:

com.myproject.core.services.SiteSettings~brand-a.cfg.json com.myproject.core.services.SiteSettings~brand-b.cfg.json

Each file creates one component instance. The Advanced series covers this pattern in depth.

Trying Configuration Locally

On the local SDK you can open the web console's Configuration page, adjust a value and watch your component react, which is a quick way to see what the configuration form looks like and which keys exist. Then write the final values into a .cfg.json file in ui.config. Console changes on a local instance are not part of your project, and they disappear when you reset the instance.

AEM as a Cloud Service vs 6.5

Topic AEM 6.5 AEM as a Cloud Service
Editing configuration Can change it in the web console or repository on a running instance Only through code and the pipeline. The Developer Console shows the result read-only
File formats .config files and sling:OsgiConfig nodes are common. Newer service packs also read .cfg.json Use .cfg.json
Location Often /apps/<project>/config.<runmode> /apps/<project>/osgiconfig/config.<runmode>
Run modes Includes custom run modes you define Only author, publish and dev, stage and prod
Secrets Typically stored in the repository or in system properties, depending on the team Cloud Manager environment variables and $[secret:...] placeholders
Changing a value Immediate, no deployment Commit, run the pipeline, deploy

The last row is the one to plan for. A "quick change" in production now means a pipeline run, unless the value is an environment variable, in which case you can update the variable and let the environment pick it up.

Common Pitfalls

  • Config file in the wrong folder. The file is valid but the folder name doesn't match the run mode, so it never applies. Check the component's configuration in the Developer Console
  • Wrong PID. A typo in the file name creates a configuration that no component reads. The PID must match the class name or your custom configurationPid
  • Component requires configuration but none is deployed for that run mode. With configurationPolicy = REQUIRE, a component is unsatisfied until its file exists
  • Secrets in plain text. If you can read a key in Git, so can everyone with repository access. Use $[secret:...]
  • Different values across Author and Publish. Remember that each service type reads its own run-mode folders, so a value set only in config.author won't reach Publish

What's Next

Many services need to read or write content. The next post covers service users and ResourceResolvers: how to get repository access without admin sessions, and how to close what you open.


Next in this series: [Service Users and ResourceResolvers: Accessing the Repository Safely]

About the author

  • AEM Technical Lead experienced with building and architecting solutions on AEM Sites and Assets. Sharing practical tutorials, real-world implementation lessons, and best practices to help fellow developers and leads navigate AEM.

How do you manage configuration across Dev, Stage and Production today? If you moved from 6.5, what was hardest to give up: changing values in the console, or custom run modes?

AEM Backend Essentials

Part 3 of 6

OSGi, services, configuration, service users, servlets and testing: the core backend skills for AEM as a Cloud Service, with 6.5 differences.

Up nextService Users and ResourceResolvers: Accessing the Repository SafelySeries: AEM Backend Essentials — Part 4