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:
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:
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:
- A Java interface defining the contract
- An implementation class registered in the service registry
- 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
Debugging Unstarted Bundles
- Go to Bundles (
/system/console/bundles)
- Find your bundle (search by
Bundle-SymbolicName)
- If bundle state is Installed (not Active), click it to inspect unresolved imports
- 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:
- Access
/system/console/jcrresolver
- Enter URL (e.g.
/content/mysite/en/about.html)
- 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.
Run Modes
Run modes determine active configurations:
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:
- Go to
/system/console/configMgr
- Search for your service name
- 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
}
}
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
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