Eclipse Equinox OSGi Runtime: Architecture Note on Dynamic Plug-in Composition
Architecture note on Eclipse Equinox OSGi runtime: bundle wiring, service registry, classloader isolation, operational diagnostics, failure modes, and design shifts for Java 17+ and modern Eclipse Platform.
24 Sept 2026, 00:47 UTC

Requirements
Eclipse IDE and RCP applications need a runtime that supports independent deployment, versioned dependencies, and hot replacement of plug-ins without restarting the JVM. Each plug-in must have an isolated classloader so that different versions of the same library can coexist. Components discover each other through a central service registry keyed by Java interface contracts rather than concrete classes. These requirements drive the OSGi bundle model and the Equinox framework's wiring logic.
Smallest Suitable Design
The atomic unit is an OSGi bundle — a JAR with a META-INF/MANIFEST.MF declaring identity, exported API packages, imported dependencies, and optional required bundles. A minimal manifest for a plug-in exposing a service interface looks like:
Bundle-SymbolicName: com.example.myservice;singleton:=true
Bundle-Version: 1.2.0
Export-Package: com.example.myservice.api;version="1.2.0"
Import-Package: org.osgi.framework;version="[1.8,2)",
com.example.other.api;version="[1.0,2)"
Service-Component: OSGI-INF/com.example.myservice.impl.MyComponent.xml
The Export-Package clause defines the public API; Import-Package declares versioned dependencies on other bundles' exports. The Service-Component header points to a Declarative Services (DS) component description (XML or annotation-generated) that registers an implementation under the interface type. Equinox creates a dedicated classloader per bundle, wires imports to matching exports respecting version ranges, and instantiates DS components when their required service references become satisfied.
Bundles transition through states: INSTALLED → RESOLVED (wiring satisfied) → STARTING → ACTIVE. Lazy activation (default) delays STARTING until another bundle requests a class or service from it. Start levels (integer Bundle-ActivationPolicy or Start-Level) control deterministic startup order; framework bundles occupy levels 1–3, application bundles should use ≥ 4.
Trust and Data Boundaries
Bundle classloaders form hard encapsulation boundaries. A bundle sees only packages it explicitly imports; reflection cannot cross the boundary unless the target package is exported and the importing bundle has the corresponding import. Services are plain Java objects passed by reference — no serialization, sandboxing, or access control by default. Sensitive data must not leak via exported packages or service properties (the Dictionary<String, Object> passed at registration).
DS component configuration (via Component XML or @Component annotations processed by bnd at build time) runs with the bundle's permissions. With the Java security manager removed in Java 17+, the bundle permission model (BundlePermission, PackagePermission) is no longer enforced. Encapsulation now relies on JPMS module boundaries (module-info.java) or external policy agents.
Operational Checks
Use the Equinox console (launch with -console -debug -clean) for live diagnostics:
osgi> ss— list all bundles with state, start level, and version. Verify target bundles reachACTIVE.osgi> diag <bundle-symbolic-name>— show missing imports, version conflicts, or split-package violations preventing resolution.osgi> services (<filter>)— inspect service registry; e.g.,services (objectClass=com.example.myservice.api.MyService)confirms registration and properties.osgi> scr list— list DS components with state (SATISFIED,ACTIVE,UNSATISFIED).UNSATISFIEDindicates missing references or configuration.
Enable DS runtime validation with -Dds.showComponents=true. For wiring graphs and service dependency trees, install the diagnostic bundle org.eclipse.equinox.diagnostics and use its console commands or REST endpoints.
Pre-runtime validation: run bnd run -r (or Maven Tycho verify) against the target platform to detect missing imports, version conflicts, and unresolved optional dependencies before launch.
Failure Modes
- Split-package conflicts: Two bundles export the same package. Equinox cannot merge them; both bundles fail to resolve. Fix: consolidate the package into a single API bundle or use
Require-Bundlewithsplit:=merge(discouraged). - Unresolved imports: Bundle stalls at
INSTALLED.diagreveals missing or version-mismatched exports. Common cause: target platform missing a required bundle or version range too narrow. - Service cycles: DS component A requires service B, which requires service A. Both remain
UNSATISFIED. Break by making one reference optional (@Reference(cardinality=ReferenceCardinality.OPTIONAL)) or using a service factory. - Lazy activation NPEs: Client assumes eager service registration, calls
ServiceTracker.getService()before activation completes, getsnull. UseServiceTracker.waitForService(timeout)or DS reference injection (field/constructor) which blocks until satisfied. - Native library mismatch:
Bundle-NativeCodedeclares OS/arch variants. Missing variant for the current platform causesClassNotFoundErrorat first native call, not at install. Always ship all target variants.
Conditions That Change the Design
Migration to OSGi R8 / Java 17+ modules: Bundles must align Export-Package with module-info.java exports clauses. The Equinox framework treats each bundle as an automatic module; --add-opens flags may be needed for legacy bundles using reflection on internal packages. Verify no InaccessibleObjectException appears in logs.
Eclipse Platform 2024-09+: Legacy Require-Bundle headers are deprecated in favor of pure Import-Package and Require-Capability (OSGi namespace osgi.extender, osgi.service). Migration: replace Require-Bundle: org.eclipse.core.runtime with Import-Package: org.eclipse.core.runtime;version="[3.18,4)" plus any extender requirements.
p2 provisioning replacing dropins: The dropins folder is unsupported. Use p2 director (eclipse -application org.eclipse.equinox.p2.director -repository <url> -installIU <iu.id>) or target platform definitions. p2 enforces strict Installable Unit (IU) metadata, preventing accidental version drift.
Security manager removal (Java 17+): Bundle permission checks (BundlePermission, PackagePermission) no longer run. If your threat model requires isolation, enforce it via JPMS strong encapsulation (module-info.java without opens), a custom SecurityManager replacement, or external policy (e.g., Java Agent with Instrumentation).
Verification Checklist
- Launch with
-console -debug -clean; runss— all application bundles showACTIVE. - Run
bnd run -rormvn tycho:verify— zero missing imports or version conflicts. - Run
scr list— all custom DS components showSATISFIEDorACTIVE. - Exercise hot-replace: update a bundle via p2 director or file replacement in the install area; verify old version unregisters services (
servicesfilter shows disappearance) and new version registers without framework restart. - Run on target JDK (17/21) with
--add-opensflags for any legacy reflective access; scan logs forInaccessibleObjectException.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.