4.2 OSGi Declarative Services, Component Lifecycle & Service Rankings

Key Takeaways

  • Modern AEM uses standard OSGi R7 Declarative Services annotations (org.osgi.service.component.annotations.*), generating XML descriptors at build time and deprecating legacy Apache Felix SCR annotations.
  • The @Component annotation controls service registration and activation. A component is delayed when it provides a service unless immediate activation is explicitly required for activation-time work.
  • The @Reference annotation configures dependency injection with precise cardinality (MANDATORY, OPTIONAL, MULTIPLE), policy (STATIC vs DYNAMIC), and LDAP filter targets.
  • The @Modified lifecycle callback allows components to process runtime configuration updates in place without undergoing disruptive deactivation and reactivation.
  • When multiple service implementations exist, OSGi resolves references by highest service.ranking integer value, breaking ties with lowest service.id (oldest registration).
Last updated: September 2026

4.2 OSGi Declarative Services, Component Lifecycle & Service Rankings

Exam Focus: OSGi Declarative Services (DS) constitutes the foundational service architecture of Adobe Experience Manager. The AD0-E128 exam tests your practical knowledge of the OSGi R7 specification (@Component, @Reference, @Activate, @Modified, @Deactivate), component configuration policies and immediate execution (immediate = true/false), reference cardinality and dynamic policies, service ranking resolution (service.ranking), type-safe OSGi configuration interfaces (@ObjectClassDefinition), and critical architectural best practices regarding thread safety and circular dependency prevention.


Evolution to OSGi R7 Declarative Services

In older versions of AEM (AEM 6.0–6.3), OSGi components were authored using Apache Felix SCR annotations (@Service, @Component, @Property, @Reference under package org.apache.felix.scr.annotations).

Modern AEM (AEM 6.4, 6.5, and AEM as a Cloud Service) has completely deprecated Felix SCR annotations in favor of the official OSGi R7 Declarative Services specification (org.osgi.service.component.annotations.*). During the Maven build, the bnd-maven-plugin (or maven-bundle-plugin) processes standard OSGi R7 annotations at compile time, generating declarative XML component descriptors inside the bundle's OSGI-INF/ directory.

AspectLegacy Apache Felix SCR (Deprecated)Modern OSGi R7 Declarative Services
Packageorg.apache.felix.scr.annotations.*org.osgi.service.component.annotations.*
Service Declaration@Service(MyService.class)@Component(service = MyService.class)
ConfigurationDictionary<String, Object> or @PropertyType-safe @ObjectClassDefinition interfaces
Build Processingmaven-scr-plugin (runtime reflection)Bnd / bnd-maven-plugin (compile-time bytecode)
Cloud Manager GateFlags warnings or deployment failuresMandatory standard for Cloud Manager pipelines

The @Component Annotation in Depth

The @Component annotation marks a Java class as an OSGi Declarative Services component managed by the Service Component Runtime (SCR).

package com.myproject.core.services.impl;

import com.myproject.core.services.PaymentGatewayService;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.propertytypes.ServiceDescription;
import org.osgi.service.component.propertytypes.ServiceRanking;

@Component(
    service = PaymentGatewayService.class,
    immediate = false,
    configurationPolicy = ConfigurationPolicy.OPTIONAL,
    property = {
        "service.vendor=MyProject",
        "process.label=Custom Payment Processor"
    }
)
@ServiceDescription("Enterprise Payment Gateway Service Implementation")
@ServiceRanking(100)
public class PaymentGatewayServiceImpl implements PaymentGatewayService {
    // implementation...
}

Key @Component Properties Explained

  • service = MyService.class: Explicitly registers the component under the specified interface. If the service element is omitted, Declarative Services can infer provided service interfaces from the implementation; use an explicit service element when the public contract should be unambiguous.
  • immediate = true vs immediate = false (Delayed / Lazy Components):
    • When immediate = false (the default for components declaring a service interface), the component is lazy-loaded. The OSGi runtime creates and activates the component instance only when another component requests it (via @Reference or bundleContext.getService()). This minimizes memory footprint and accelerates AEM bundle startup.
    • When immediate = true, the component activates as soon as its mandatory references and configuration are satisfied, even if no consumer has requested its service. Use this when activation itself must perform work and no service consumer would otherwise trigger a delayed component. Do not assume every event handler, job consumer, or scheduled service requires immediate activation; whiteboard registrations and provided services can activate through normal DS demand.
  • configurationPolicy:
    • ConfigurationPolicy.OPTIONAL (default): Activates with or without an OSGi configuration.
    • ConfigurationPolicy.REQUIRE: Activation is blocked until an explicit configuration for this component's PID is created in ConfigurationAdmin.
    • ConfigurationPolicy.IGNORE: Completely ignores any OSGi configuration files.

Dependency Injection with @Reference

The @Reference annotation injects required or optional OSGi services into a component.

@Component(service = OrderProcessingService.class)
public class OrderProcessingServiceImpl implements OrderProcessingService {

    // Mandatory, static reference (1..1)
    @Reference
    private PaymentGatewayService paymentService;

    // Optional dynamic reference with LDAP filter target
    @Reference(
        cardinality = ReferenceCardinality.OPTIONAL,
        policy = ReferencePolicy.DYNAMIC,
        policyOption = ReferencePolicyOption.GREEDY,
        target = "(integration.environment=production)"
    )
    private volatile AnalyticsService analyticsService;

    // Multiple references (0..n)
    @Reference(
        cardinality = ReferenceCardinality.MULTIPLE,
        policy = ReferencePolicy.DYNAMIC
    )
    private final List<OrderValidator> validators = new CopyOnWriteArrayList<>();
}

Reference Configuration Options

1. cardinality

Defines the multiplicity and necessity of the dependency:

  • ReferenceCardinality.MANDATORY (1..1, default): Exactly one service instance must be bound. The consuming component cannot activate until this dependency is satisfied.
  • ReferenceCardinality.OPTIONAL (0..1): The dependency can be null. The consuming component activates regardless.
  • ReferenceCardinality.MULTIPLE (0..n): Injects zero or more instances of the service interface.
  • ReferenceCardinality.AT_LEAST_ONE (1..n): Requires at least one service implementation to activate.

2. policy: STATIC vs DYNAMIC

  • ReferencePolicy.STATIC (default): If the referenced service is modified, restarted, or replaced, the consuming component is deactivated and reactivated. This guarantees state consistency but causes cascade restarts across connected services.
  • ReferencePolicy.DYNAMIC: The referenced service can be bound, unbound, or replaced dynamically without deactivating the consuming component. Fields using dynamic policy should be marked volatile or managed via thread-safe collections (CopyOnWriteArrayList).

3. policyOption: RELUCTANT vs GREEDY

  • ReferencePolicyOption.RELUCTANT (default): If a new, higher-ranked matching service becomes available, the component keeps the currently bound service.
  • ReferencePolicyOption.GREEDY: If a higher-ranked service appears, OSGi immediately rebinds the reference to the new, superior service.

4. target: LDAP Filter Expressions

Restricts injected services based on OSGi properties:

  • target = "(service.ranking>=500)"
  • target = "(&(type=cloud)(region=us-east))"
  • target = "(component.name=com.myproject.SpecificService)"

Declarative Services Lifecycle: @Activate, @Modified, @Deactivate

OSGi components progress through an explicit lifecycle managed by the Service Component Runtime.

@Component(service = ConfigurationDemoService.class)
@Designate(ocd = ConfigurationDemoService.Config.class)
public class ConfigurationDemoServiceImpl implements ConfigurationDemoService {

    private static final Logger LOG = LoggerFactory.getLogger(ConfigurationDemoServiceImpl.class);

    @ObjectClassDefinition(name = "Configuration Demo Service Config")
    public @interface Config {
        @AttributeDefinition(name = "API Endpoint")
        String apiEndpoint() default "https://api.example.com/v1";

        @AttributeDefinition(name = "Timeout in Seconds")
        int timeoutSeconds() default 30;
    }

    private volatile Config config;

    @Activate
    protected void activate(Config config, ComponentContext componentContext, BundleContext bundleContext) {
        this.config = config;
        LOG.info("Service activated with endpoint: {}", config.apiEndpoint());
    }

    @Modified
    protected void modified(Config config) {
        this.config = config;
        LOG.info("Service configuration dynamically refreshed: timeout = {}", config.timeoutSeconds());
    }

    @Deactivate
    protected void deactivate(ComponentContext componentContext) {
        LOG.info("Service deactivating; releasing resources");
        // Close background executors, network sockets, or caches
    }
}

Lifecycle Callback Mechanics

  1. @Activate: Executed once all mandatory references are bound and configuration constraints are satisfied. Used to initialize connections, start thread pools, or validate credentials.
  2. @Modified: Crucial for zero-downtime updates. When an administrator updates an OSGi configuration via .cfg.json or Web Console, OSGi inspects the component. If @Modified is present, OSGi invokes it directly with the new configuration. The component updates its internal state in place without undergoing @Deactivate and @Activate cycles, avoiding broken user sessions. If @Modified is omitted, OSGi is forced to tear down and reconstruct the entire component.
  3. @Deactivate: Executed when the component is stopped, the host bundle is stopped, or a mandatory reference is unbound. Must perform complete resource cleanup (e.g., closing background threads, clearing caches) to prevent memory leaks in the OSGi JVM.

Service Ranking & Multi-Implementation Resolution

In enterprise AEM systems, multiple OSGi services often implement the same public interface (e.g., a standard fallback service and a specialized client override).

When a consuming component declares a 1..1 reference to the shared interface without an LDAP target filter, OSGi resolves the tie using a deterministic algorithm:

  1. service.ranking (Primary Criterion): The service with the highest integer ranking is selected. Rankings can be positive, zero (default), or negative (e.g., service.ranking:Integer=1000 overrides service.ranking:Integer=100).
  2. service.id (Tie-Breaker): If multiple services have identical service.ranking values, OSGi selects the service with the lowest service.id (meaning the service that was registered first in the OSGi runtime).

Setting Service Ranking

In OSGi R7, ranking can be set via @Component(property = {"service.ranking:Integer=1000"}) or using the type-safe @ServiceRanking(1000) annotation:

@Component(service = CustomWorkflowRule.class)
@ServiceRanking(5000) // Ensures this custom rule executes before default AEM rules (ranking 0)
public class CustomWorkflowRuleImpl implements CustomWorkflowRule { ... }

Type-Safe OSGi Configurations (@ObjectClassDefinition)

Modern AEM uses standard Java annotations to define typed OSGi configuration schemas:

  • @ObjectClassDefinition (OCD): Declares the metadata container for the configuration.
  • @AttributeDefinition: Defines individual configuration properties, including labels, descriptions, data types (STRING, INTEGER, BOOLEAN, PASSWORD), and default values.
  • @Designate(ocd = MyConfig.class): Binds the configuration definition to the component implementation.

Single vs Factory Configurations

  • Standard Configuration: A single configuration instance per service.
  • Factory Configuration (@Designate(ocd = MyConfig.class, factory = true)): Allows creating multiple independent runtime instances of the service, each with its own configuration. In AEM as a Cloud Service, factory configurations are deployed in ui.config using .cfg.json files with sub-service identifiers: com.myproject.core.services.impl.ExportServiceImpl~us_region.cfg.json

Thread Safety, State Management & Anti-Patterns

Because OSGi components are singletons shared across the entire AEM JVM, failure to observe concurrency standards results in severe production outages.

Critical Rules for Production Services

1. Never Store Stateful Objects as Instance Fields

Multiple HTTP request threads execute service methods concurrently. Storing request-specific or session-specific objects as service fields is a fatal bug:

@Component(service = BadService.class)
public class BadServiceImpl implements BadService {
    // FATAL: ResourceResolver is NOT thread-safe!
    private ResourceResolver requestResolver; 
}

Concurrent requests will overwrite and mutate requestResolver, resulting in unhandled IllegalStateException, race conditions, and repository session corruption.

Correct Pattern: Scope ResourceResolver locally within method calls, always using try-with-resources:

public void processContent(ResourceResolverFactory factory) {
    try (ResourceResolver resolver = factory.getServiceResourceResolver(authInfo)) {
        // execute operations safely...
    } catch (LoginException e) {
        LOG.error("Failed to obtain service resolver", e);
    }
}

2. Managing Dynamic Configuration State

When configurations change via @Modified, assign the new configuration to a volatile reference to ensure thread visibility across CPU caches without heavy synchronization locking:

private volatile Config config;

@Modified
protected void modified(Config config) {
    this.config = config; // Thread-safe atomic pointer swap
}

3. Preventing Circular Dependencies

A circular dependency occurs when ServiceA requires ServiceB, and ServiceB requires ServiceA. If both references are ReferenceCardinality.MANDATORY and ReferencePolicy.STATIC, neither service can ever activate. Both remain indefinitely in the UNSATISFIED_REFERENCE state, deadlocking the components.

Resolution Strategies:

  • Refactor Domain Logic: Extract the shared functionality into a third intermediary service (ServiceC).
  • Break Cardinality: Change one reference to ReferenceCardinality.OPTIONAL and dynamic policy.
  • Event-Driven Decoupling: Decouple the services asynchronously using Apache Sling Jobs or OSGi EventAdmin.
Test Your Knowledge

AEM has two OSGi services implementing the same PaymentGatewayService interface. Implementation A has service.ranking = 100 and service.id = 250. Implementation B has service.ranking = 500 and service.id = 320. A consuming service declares @Reference private PaymentGatewayService paymentService;. Which implementation will OSGi inject into the consumer, and why?

A
B
C
D
Test Your Knowledge

An OSGi service reads third-party API credentials via a typed @ObjectClassDefinition configuration. When an administrator updates the API key in the configuration via .cfg.json, the service deactivates and reactivates, causing temporary dropped connections. How should the service be modified to apply configuration changes in place without deactivating the component?

A
B
C
D
Test Your Knowledge

Why is storing an active ResourceResolver or JCR Session as a private instance field in an OSGi service considered a severe anti-pattern in AEM development?

A
B
C
D
Test Your Knowledge

Under what circumstance MUST an OSGi component explicitly specify immediate = true on its @Component annotation in AEM?

A
B
C
D