Skip to main content

OSGi in AEM: Bundles, Components and the Lifecycle

Series: AEM Backend Essentials — Part 1

Updated 6 min readMarkdown
Part of seriesAEM Backend Essentials
OSGi in AEM: Bundles, Components and the Lifecycle

Every piece of Java you write for AEM runs inside OSGi. Sling Models, servlets, workflow steps, schedulers and your own services are all OSGi components living in bundles. If you understand how OSGi starts, wires and stops your code, many "it works locally but not in the cloud" mysteries become easy to diagnose. This post covers the basics and the lifecycle, with a focus on AEM as a Cloud Service and notes on where 6.5 differs.

What OSGi Gives You

OSGi is a module system for Java. Instead of one big application, AEM is a collection of bundles, each a JAR file with a manifest that says what it exports (offers to others) and what it imports (needs from others).

  • A bundle is the unit of deployment. Your core module builds one
  • A component is a class managed by OSGi, created and destroyed by the framework
  • A service is a component that other components can use through an interface (the next post covers this)

The archetype's core module uses the bnd tooling to generate the manifest for you, so you rarely edit it by hand.

Bundle States

A bundle moves through these states:

State Meaning
Installed Uploaded, but its dependencies aren't resolved yet
Resolved All imports found, ready to start
Starting Being activated
Active Running, and its components can be created

The state you'll meet most often in trouble is Installed. A bundle stuck there almost always has an unresolved import, for example a package your code uses that no active bundle exports at a matching version. The usual fixes are to depend only on APIs from the AEM SDK API, check version ranges, and avoid pulling in third-party libraries the runtime doesn't provide.

Declarative Services Components

You write OSGi components with the annotations from org.osgi.service.component.annotations. A minimal service with a configuration looks like this:

package com.myproject.core.services;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

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

    @ObjectClassDefinition(name = "My Project - Greeting Service")
    public @interface Config {
        @AttributeDefinition(name = "Greeting")
        String greeting() default "Hello";
    }

    private volatile String greeting;

    @Activate
    protected void activate(Config config) {
        this.greeting = config.greeting();
        LOG.info("GreetingService activated with greeting '{}'", greeting);
    }

    @Modified
    protected void modified(Config config) {
        this.greeting = config.greeting();
        LOG.info("GreetingService configuration changed");
    }

    @Deactivate
    protected void deactivate() {
        LOG.info("GreetingService deactivated");
    }

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

The interface is a plain Java interface with one method, String greet(String name).

The Component Lifecycle

Bundles have states, and so do components. A component is created by the framework, and the three lifecycle methods are your hooks:

Annotation When it runs Typical use
@Activate Once, when all required references and configuration are available Read configuration, initialize caches, register with other systems
@Modified When the component's configuration changes, without restarting it Update fields from the new configuration
@Deactivate When the component is stopped: bundle stop, config removed or a reference is lost Release resources, close clients, cancel work

A component is only active when it is satisfied: every mandatory reference is available and, if you required one, its configuration exists. Otherwise it stays unsatisfied and nothing is created. This is the single most common reason a service "doesn't work": it was never activated.

Rules of Thumb for the Lifecycle

  • Keep activate fast. Heavy work (remote calls, big queries) delays startup and can fail the whole component
  • Treat components as singletons shared by many threads. Avoid mutable fields, or make them volatile or thread-safe
  • Undo in deactivate what you did in activate. Close clients, stop timers, deregister listeners
  • Never do blocking work for a long time in lifecycle methods
  • Log at activation at INFO level. It makes it easy to see in the logs which configuration a component started with

Seeing What's Running

You can inspect bundles, components and services at runtime.

Environment Tool
Local SDK The Felix web console at /system/console: Bundles, Components, Configuration, Services
AEM as a Cloud Service The Developer Console (Status section) for a read-only view of bundles, components, configurations and services, plus logs

For a component that doesn't work, check in this order:

  1. Is the bundle Active?
  2. Is the component listed, and in which state? An "unsatisfied reference" or "missing configuration" message tells you exactly what's missing
  3. Does the log show your activate message?

AEM as a Cloud Service vs 6.5

Topic AEM 6.5 AEM as a Cloud Service
Installing bundles Can upload and start or stop bundles in the web console Deployed only through the Cloud Manager pipeline. No manual installs on Stage and Production
Changing a running component Possible in the web console Not possible. Change the code or configuration and redeploy
Inspecting at runtime Full Felix web console Read-only through the Developer Console
API surface Full product jars Use the AEM SDK API, which exposes only the supported, cloud-compatible APIs
Updates You choose when to apply service packs Continuous. Your bundle must stay compatible with the platform's updated APIs

Common Pitfalls

  • Bundle stays Installed. Unresolved import. Read the message in the console, then fix the dependency
  • Component never activates. A mandatory reference is missing, or a configuration required by configurationPolicy = REQUIRE hasn't been deployed for that run mode
  • Works locally, fails on Cloud. The local SDK is a single instance with a forgiving setup. Always test on a Dev environment
  • Work in activate that fails. An exception there leaves the component unsatisfied. Catch, log and fail clearly
  • Forgetting service = ... in @Component. The component exists but isn't registered as a service, so nothing can inject it

What's Next

You now know how components start and stop. The next post shows how components find and use each other: services, references and the common wiring errors.


Next in this series: [OSGi Services and References: Wiring Your Code Together]

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.

What was the first OSGi problem that really confused you: a bundle stuck in Installed, or a component that never activated? Share the symptom and what fixed it, because those stories help others who search for the same thing.

AEM Backend Essentials

Part 1 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 nextOSGi Services and References: Wiring Your Code TogetherSeries: AEM Backend Essentials — Part 2