For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt, and this page is available as Markdown at https://nhanbe.id.vn/aem/foundation/osgi-fundamentals.md.

OSGi is the module system managing all Java code in AEM. Every Java piece — your code, AEM's code, third-party libraries — runs inside an **OSGi

OSGi Fundamentals

OSGi Fundamentals in AEM

OSGi is the module system managing all Java code in AEM. Every Java piece — your code, AEM's code, third-party libraries — runs inside an OSGi bundle. Understanding OSGi is essential for writing services, managing configurations, and debugging.


1. What is OSGi?

OSGi (Open Services Gateway initiative) is a modular architecture specification for Java applications. In AEM, it is implemented by Apache Felix.

Core concepts:

ConceptDescription
BundleA JAR file with special metadata. It is the deployment unit
ServiceA Java interface registered in the service registry. Other bundles can look up and consume it
ComponentA managed Java class that provides services and consumes other services
ConfigurationKey-value set used to alter component behavior without redeploying

2. Bundles

A bundle is a JAR file declaring OSGi metadata in MANIFEST.MF:

Bundle-SymbolicName: com.mysite.core
Bundle-Version: 1.0.0
Import-Package: org.apache.sling.api, javax.inject
Export-Package: com.mysite.core.models

In Maven projects, the core/ module produces an OSGi bundle. The maven-bundle-plugin (or bnd-maven-plugin) automatically generates the manifest.

Bundle Lifecycle

Each bundle passes through these states:

StateMeaning
InstalledBundle loaded into container, but dependencies remain unresolved
ResolvedAll imports satisfied; ready to start
ActiveRunning — services registered
StoppingShutting down

Note: If a bundle gets stuck in the Installed state instead of Active, dependencies are unresolved. Web Console displays missing packages.


3. Services

Services are the primary communication method between Java components in OSGi. A service consists of:

  1. A Java interface defining the contract
  2. An implementation class registered in the service registry
  3. Consumers looking up and using the service

Declarative Services (DS)

Modern AEM uses OSGi Declarative Services with annotations:

Interface Definition:

package com.mysite.core.services;

public interface GreetingService {
    String greet(String name);
}

Implementation:

package com.mysite.core.services.impl;

import com.mysite.core.services.GreetingService;
import org.osgi.service.component.annotations.Component;

@Component(service = GreetingService.class)
public class GreetingServiceImpl implements GreetingService {

    @Override
    public String greet(String name) {
        return "Hello, " + name + "!";
    }
}

The @Component annotation informs OSGi:

  • This class is a managed component
  • It provides the GreetingService service interface
  • OSGi manages its lifecycle and registers it in the service registry

Consuming Services

Services reference other services using @Reference:

import com.mysite.core.services.GreetingService;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

@Component(service = SomeOtherService.class)
public class SomeOtherServiceImpl implements SomeOtherService {

    @Reference
    private GreetingService greetingService;

    public void doSomething() {
        String message = greetingService.greet("AEM");
    }
}

Inside Sling Models, use @OSGiService:

@Model(
    adaptables = {SlingHttpServletRequest.class, Resource.class},
    adapters = {GreetingModel.class, ComponentExporter.class},
    resourceType = GreetingModel.RESOURCE_TYPE,
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class GreetingModel {

    @OSGiService
    private GreetingService greetingService;

    @PostConstruct
    public void init() {
        final String message = greetingService.greet("AEM");
    }
}

OSGi automatically injects the service. If the service is unavailable, the component will not activate — known as dependency satisfaction.

Service Ranking

When multiple implementations exist for an interface, OSGi uses service ranking to choose which is injected:

@Component(
    service = GreetingService.class,
    property = {
        "service.ranking:Integer=100"
    }
)
public class CustomGreetingServiceImpl implements GreetingService {
    // Priority implementation over lower ranking ones
}

Higher ranking wins when picking a single service (default cardinality for @Reference is mandatory/unary). This is how default AEM services are overridden.

Control cardinality and target filtering on @Reference:

// Optional: component activates even if no implementation exists
@Reference(cardinality = ReferenceCardinality.OPTIONAL)
private GreetingService greetingService;

// Multiple: inject all implementations (List)
@Reference(cardinality = ReferenceCardinality.MULTIPLE)
private List<GreetingService> greetingServices;

// Target filter: pick specific implementation by property
@Reference(target = "(service.pid=com.mysite.core.services.impl.CustomGreetingServiceImpl)")
private GreetingService greetingService;

4. OSGi Web Console

Web Console is a versatile debugging tool. Access at:

http://localhost:4502/system/console

Credentials: admin / admin

Important Console Pages

ConsoleURLDisplays
Bundles/system/console/bundlesAll installed bundles and status
Components/system/console/componentsAll DS components (active, satisfied, unsatisfied)
Services/system/console/servicesAll registered services
Configuration/system/console/configMgrAll OSGi configurations
Resource Resolver/system/console/jcrresolverTest resource resolution
Log Support/system/console/slinglogLogging configuration

Debugging Unstarted Bundles

  1. Go to Bundles (/system/console/bundles)
  2. Find your bundle (search by Bundle-SymbolicName)
  3. If bundle state is Installed (not Active), click it to inspect unresolved imports
  4. Missing packages identify unresolved dependencies

Common Causes:

  • Third-party library not embedded into bundle
  • Package version conflict between custom code and AEM
  • Missing AEM API dependency

Test Resource Resolution

The Sling Resource Resolver page tests URL resolution:

  1. Access /system/console/jcrresolver
  2. Enter URL (e.g. /content/mysite/en/about.html)
  3. Click Resolve to view matched resource and rendering script

5. OSGi Configurations

Configurations modify service behavior without code edits. Stored in JCR and varied by run mode (author, publish, dev, stage, prod).

Declaring Configurations via Annotations

Use @ObjectClassDefinition and @AttributeDefinition:

package com.mysite.core.services.impl;

import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

@ObjectClassDefinition(name = "Greeting Service Configuration")
public @interface GreetingServiceConfig {

    @AttributeDefinition(
        name = "Default Greeting",
        description = "The greeting prefix to use"
    )
    String greeting_prefix() default "Hello";

    @AttributeDefinition(
        name = "Enabled",
        description = "Whether the service is active"
    )
    boolean enabled() default true;
}

Consuming Configurations in Services

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.metatype.annotations.Designate;

@Component(service = GreetingService.class)
@Designate(ocd = GreetingServiceConfig.class)
public class GreetingServiceImpl implements GreetingService {

    private String prefix;
    private boolean enabled;

    @Activate
    protected void activate(GreetingServiceConfig config) {
        this.prefix = config.greeting_prefix();
        this.enabled = config.enabled();
    }

    @Override
    public String greet(String name) {
        if (!enabled) {
            return "";
        }
        return prefix + ", " + name + "!";
    }
}

The @Activate method executes when the component starts and receives active configurations.

Configuration Files in Repository

Configurations are stored as .cfg.json files inside module ui.config:

ui.config/src/main/content/jcr_root/apps/mysite/osgiconfig/
├── config/                          # All environments
│   └── com.mysite.core.services.impl.GreetingServiceImpl.cfg.json
├── config.author/                   # Author only
├── config.publish/                  # Publish only
├── config.dev/                      # Dev environment
├── config.stage/                    # Stage environment
└── config.prod/                     # Production

File content:

{
  "greeting.prefix": "Welcome",
  "enabled": true
}

Naming Convention: Annotation method greeting_prefix() maps to config key greeting.prefix. OSGi converts underscores (_) in method names to dots (.) in configuration keys.

Method NameConfig Property Key
greeting_prefix()greeting.prefix
my__property()my_property
trailing_()trailing

Run Modes

Run modes determine active configurations:

Run ModeActive Context
authorAuthor instance
publishPublish instance
devDevelopment environment
stageStage environment
prodProduction environment

Configurations are cumulative — config/ applies universally, while specific folders add/override properties when matching run modes are active.

Tip: Keep defaults in config/, adding specific environment overrides in respective folders.

Editing Configurations via Web Console

During development, edit configurations directly:

  1. Go to /system/console/configMgr
  2. Search for your service name
  3. Edit values and click Save

Important: Console edits are transient — lost upon instance restart. For durable configurations, use .cfg.json files in ui.config.


6. Component Lifecycle Annotations

In addition to @Activate, OSGi supports other lifecycle annotations:

@Component(service = MyService.class)
public class MyServiceImpl implements MyService {

    @Activate
    protected void activate(MyConfig config) {
        // Called when component starts
        // Initialize resources, read config
    }

    @Modified
    protected void modified(MyConfig config) {
        // Called when configuration updates at runtime
        // Update internal state without restarting
    }

    @Deactivate
    protected void deactivate() {
        // Called when component stops
        // Release resources
    }
}
AnnotationInvocation Trigger
@ActivateComponent starts (all dependencies satisfied)
@ModifiedConfiguration changes while component is active
@DeactivateComponent stops (bundle stops, or dependency lost)

7. Sling Models and OSGi

Sling Models are not standard Declarative Services components (@Component). Instead, Sling Model metadata is discovered by the Sling Models framework running inside OSGi:

@Model(adaptables = Resource.class, adapters = MyModel.class)
public class MyModelImpl implements MyModel {
    // ...
}

The core/ bundle still requires proper metadata and dependencies because model discovery and adaptation execute within the OSGi runtime.


8. Troubleshooting Guide

SymptomFirst CheckCommon Fix
Bundle stuck in Installed/system/console/bundles — inspect imports/exportsAdd/fix dependencies or package versions
Component unsatisfied/system/console/components — inspect referencesRegister missing service or relax reference cardinality
Config not applying/system/console/configMgr — verify PID and valuesFix PID/filename, verify run mode folder
Unexpected service implementation/system/console/services — inspect rankingAdjust service.ranking and target filters
Works on local SDK, fails on cloudCloud logs + committed config filesCommit console edits as .cfg.json files in ui.config

9. Common OSGi Patterns in AEM

Pattern 1: Service Interface + Implementation

Separate interfaces from implementations:

com.mysite.core.services/
├── GreetingService.java          # Interface (exported)
└── impl/
    └── GreetingServiceImpl.java  # Implementation (not exported)

The interface resides in an exported package. The implementation lives in an unexported impl sub-package, ensuring encapsulation.

Pattern 2: Scheduler Service

Execute code on schedule:

@Component(
    service = Runnable.class,
    property = {
        "scheduler.expression=0 0 2 * * ?",  // Every day at 2:00 AM
        "scheduler.concurrent=false"
    }
)
public class DailyCleanupJob implements Runnable {

    @Override
    public void run() {
        // Cleanup logic
    }
}

Pattern 3: Event Handler

React to OSGi events:

@Component(
    service = EventHandler.class,
    property = {
        "event.topics=org/apache/sling/api/resource/Resource/CHANGED"
    }
)
public class ResourceChangeHandler implements EventHandler {

    @Override
    public void handleEvent(Event event) {
        String path = (String) event.getProperty("path");
        if (path != null) {
            // Handle resource change at path
        }
    }
}

AEM 6.5 vs AEMaaCS Note: Classic replication events (com/day/cq/replication/job/publish) apply to AEM 6.5. In AEM as a Cloud Service, content distribution replaces classic replication. For resource tracking, prefer ResourceChangeListener or OSGi events with resource topics.


Summary

OSGi in AEM
├── Bundle
│   ├── JAR file with special MANIFEST.MF
│   ├── Lifecycle: Installed → Resolved → Active
│   └── Debug at /system/console/bundles
├── Service
│   ├── Java interface + implementation
│   ├── Register via @Component
│   ├── Inject via @Reference / @OSGiService
│   └── Ranking for overrides
├── Configuration
│   ├── @ObjectClassDefinition / @AttributeDefinition
│   ├── .cfg.json files in ui.config
│   └── Run modes: author, publish, dev, stage, prod (cumulative)
├── Lifecycle Annotations
│   ├── @Activate — component starts
│   ├── @Modified — config changes at runtime
│   └── @Deactivate — component stops
├── Web Console
│   ├── /system/console/bundles
│   ├── /system/console/components
│   └── /system/console/configMgr
└── Patterns
    ├── Service interface + impl separation
    ├── Scheduler — Runnable + cron expression
    └── Event Handler — react to OSGi events