AEM's dependency injection: comparing SCR annotations to OSGi R6
CIRCUIT brought AEM engineers together in Chicago for the practical work behind Adobe Experience Manager: wiring services, shipping front ends, and keeping distributed systems maintainable. Dependency injection sat at the centre of much of that architecture because it lets a component declare what it needs without constructing every collaborator itself. Learn more about Isorc2015.org.
The choice between Apache Felix Service Component Runtime annotations and OSGi Declarative Services therefore matters. Both approaches describe OSGi components, but they differ in metadata, lifecycle hooks, reference semantics, and portability. Getting that distinction right avoids duplicate wiring code, fragile service lookups, and components that appear to work in one bundle while failing after deployment. Learn more about Registration.
The comparison also needs historical perspective. The 2015 and 2016 CIRCUIT programmes captured AEM teams using Sling and OSGi services in production, often mixing application code, third-party bundles, and cloud integrations. OSGi R6 and Declarative Services 1.3 later gave this landscape a stronger portable standard, so existing SCR code does not automatically become obsolete. Learn more about Ad14chile.com.
For Australian teams, the decision is rarely purely theoretical. Delivery groups may be distributed across Sydney, Melbourne, Brisbane, and Perth, while the same component may support government services, banking journeys, or retail campaigns. Clear dependency boundaries improve maintainability, repeatable releases, and incident response across those environments.
Dependency injection in the AEM component model
AEM applications commonly run as OSGi bundles inside Sling. A service component can publish an OSGi service, consume other services, and react when those references change. Dependency injection moves that assembly relationship into source code or generated metadata, leaving the runtime container to resolve bindings and manage component lifecycle.
That matters for front-end and backend integration. A Sling Model can use injected services, while a servlet, scheduler, or workflow handler relies on component-managed collaborators. The result is easier unit testing because required dependencies can be supplied directly rather than mocked around a global service registry. It also gives architects a visible dependency graph instead of scattered service lookups.
Context from the period can be found in the 2015 ISORC programme, which sits alongside the conference ecosystem around modular Java and service-oriented development. The durable lesson is not that one annotation set wins in isolation, but that AEM teams need a repeatable contract for activation, reference binding, configuration, and deactivation.
What SCR annotations actually declare
Apache Felix SCR annotations, commonly imported from org.apache.felix.scr.annotations, are an AEM and Sling tradition. @Component identifies a component class, @Reference declares an OSGi service dependency, and @Activate and @Deactivate mark lifecycle methods. Properties can be supplied with @Property or through inherited configuration mechanisms, depending on the AEM release and Felix SCR version.
Reference declarations can express cardinality and update policy, including mandatory or optional references and static or dynamic binding. A static reference is resolved before activation and remains fixed, while a dynamic reference can change without the component being recreated. These semantics must be understood precisely: a missing mandatory service prevents normal activation, whereas an optional service may legitimately be absent.
SCR is closely associated with Apache Felix and AEM’s implementation lineage, which can help when teams need predictable behaviour inside a supported product. The cost is tighter coupling to that ecosystem. Annotation packages, generated XML descriptors, and Felix-specific tooling may not transfer cleanly to another OSGi container, and mixed SCR and Declarative Services components can duplicate services if their activation settings are careless.
What OSGi R6 changes through Declarative Services
OSGi R6 includes Declarative Services 1.3 within the broader OSGi standards framework. Its annotations live in org.osgi.service.component.annotations and provide equivalents for components, references, activation, modification, and deactivation. A developer can therefore describe a component through a vendor-neutral API while the container generates a standard component descriptor.
The important comparison is semantic rather than cosmetic. Both models support declaring service dependencies and controlling whether a reference is mandatory, optional, multiple, or dynamic. OSGi Declarative Services also gives non-Felix containers a common interpretation of component metadata and property handling. That portability is valuable for organisations standardising bundles, although migration does not guarantee identical runtime behaviour because SCR extensions and implementation details still differ.
Archived CIRCUIT registration details show how technical conferences were packaged as shared learning rather than product launches. For AEM architects, the practical question is which specification the target baseline supports, what libraries the organisation permits, and whether portability has enough value to justify descriptor conversion and regression testing.
Practical recommendations for AEM teams
AEM’s dependency injection should be treated as an architectural governance decision, not a convenient import change. Begin by confirming the exact AEM, Sling, Felix SCR, and Declarative Services versions in every environment. A developer laptop, immutable cloud image, and lower environment can otherwise expose different annotation processors or metadata behaviour.
Local governance adds another test. For services covered by the Privacy Act 1988 and the Australian Privacy Principles, dependency design can support data minimisation, access control, and auditable change, but annotation choice does not make data handling compliant. Teams should keep personal-data flows visible when introducing service references.
- Confirm the runtime contract by checking the AEM and Sling baseline, enabled SCR or Declarative Services implementation, annotation processor, and deployment descriptor before selecting a model.
- Prefer constructor or explicit method injection for required collaborators when the supported runtime provides it; reserve field injection for optional references or compatibility needs.
- State cardinality and binding policy explicitly so a missing, duplicated, or replaced service cannot produce accidental activation and inconsistent requests.
- Keep activation and deactivation methods idempotent, with explicit handling for every reference that may arrive or disappear during the component’s life.
- Centralise configuration and property types, and keep credentials in approved secrets or configuration services rather than embedding values in component metadata.
- Migrate incrementally by running equivalent SCR and Declarative Services versions through unit, integration, and deployment tests, then monitor service registration and unbinding in production.
These choices become more visible as digital services scale. Australian consumers commonly handle banking, shopping, and transport information on a smartphone during commutes, so an injected dependency may sit behind several latency-sensitive paths even when the interface feels like a single app. The architecture should make those paths observable without turning every class into a container of hidden collaborators.
Campaign components also need rules that extend beyond successful injection. Commercial messaging workflows should support consent, suppression, and opt-out decisions consistent with the Spam Act 2003. Similarly, Australian Consumer Law and ACCC expectations mean A/B test variants must not misrepresent prices, availability, or material terms. Test behaviour, not merely annotation compilation, by covering activation with missing references, dynamic rebinding, component deactivation, and service unregistration.
Turn the comparison into an implementation decision
Broader Adobe community material, including AD14 Chile, shows why implementation knowledge travels beyond a single conference. Yet the best way to compare the two approaches is to select one realistic component, such as a Target activity orchestrator, a scheduled synchronisation job, or a workflow adapter, and model its lifecycle under each model.
Start with the Adobe Target testing guidance from CIRCUIT, adapt it to your own consent and experiment rules, and use it as an integration scenario. A/B workflows reveal whether a reference is resolved at the right time, whether losing an external service blocks activation, and whether retry behaviour remains safe.
Build the comparison, review the recorded CIRCUIT sessions, and share the resulting design guidance with your architects and engineers. A useful outcome is a documented decision standard: supported annotations, naming rules, lifecycle responsibilities, test cases, and an owner for future upgrades. That standard turns dependency injection from repeated local preference into consistent platform practice.