Last Updated:
How to Run Java Subprocesses in Equinox with Dynamic Bundle Installation Using Feature.xml (OSGi Guide)
OSGi (Open Service Gateway Initiative) has revolutionized modular application development by enabling dynamic componentization, versioning, and lifecycle management of software bundles. Equinox, the reference implementation of the OSGi Core and Compendium specifications, is widely used to build modular Java applications, from Eclipse IDE plugins to enterprise systems.
In complex OSGi applications, two common requirements often arise:
- Running Java subprocesses to isolate resource-intensive or third-party tasks (e.g., legacy tools, batch jobs) from the main Equinox runtime.
- Dynamically installing bundles/features to extend application functionality without restarting the Equinox container (e.g., adding new plugins on-demand).
This guide dives deep into both topics, with a focus on integrating them: running subprocesses that trigger dynamic bundle installation using Feature.xml—a key OSGi artifact for aggregating and managing bundles. By the end, you’ll be able to build flexible, extensible Equinox applications that combine subprocess isolation with dynamic modularity.
Table of Contents#
- Prerequisites
- Understanding the Basics
- 2.1 OSGi and Equinox
- 2.2 Java Subprocesses in OSGi
- 2.3 Feature.xml: Bundles Aggregated
- Setting Up the Equinox Environment
- Dynamic Bundle Installation with Feature.xml
- 4.1 Anatomy of a Feature.xml
- 4.2 Creating a Feature.xml
- 4.3 Installing Features Dynamically with Equinox APIs
- Running Java Subprocesses in Equinox
- 5.1 Executing Subprocesses: ProcessBuilder vs. Runtime.exec
- 5.2 Handling Classpath and Environment in Subprocesses
- 5.3 Subprocess Lifecycle Management
- Integration: Subprocesses Triggering Dynamic Bundle Installation
- 6.1 Use Case: Subprocess-Generated Bundles
- 6.2 Inter-Process Communication (IPC) for Coordination
- 6.3 End-to-End Example
- Troubleshooting Common Issues
- Conclusion
- References
Prerequisites#
Before diving in, ensure you have the following:
- Java Development Kit (JDK) 8+: Equinox and OSGi bundles typically require Java 8 or higher.
- Equinox SDK: Download from the Eclipse Equinox website.
- Eclipse IDE (Optional): For bundle/feature development (recommended for beginners).
- Basic OSGi Knowledge: Familiarity with bundles,
BundleContext, and OSGi service concepts. - Maven/Gradle (Optional): For building bundles; alternatively, use Eclipse’s PDE (Plug-in Development Environment).
Understanding the Basics#
2.1 OSGi and Equinox#
OSGi is a modularization standard for Java that defines a component model (bundles), a service registry, and a lifecycle management system for bundles. Bundles are self-contained modules with metadata (e.g., MANIFEST.MF) describing dependencies, exports, and activation policies.
Equinox is Eclipse’s implementation of the OSGi Core and Compendium specifications. It provides a runtime environment (Equinox OSGi Framework) for executing bundles, along with tools for provisioning, updating, and managing bundles dynamically.
2.2 Java Subprocesses in OSGi#
A subprocess is a separate operating system process spawned from a parent process (e.g., the Equinox JVM). In OSGi, subprocesses are useful for:
- Isolating unstable or resource-heavy tasks (e.g., image processing, data scraping).
- Running legacy code that isn’t OSGi-aware.
- Parallelizing tasks without blocking the main Equinox runtime.
Challenges include:
- Classpath Isolation: Equinox uses a custom classloader hierarchy, so subprocesses may not inherit the parent’s classpath.
- Lifecycle Coordination: Ensuring subprocesses terminate when the parent bundle stops.
- Inter-Process Communication (IPC): Exchanging data between the subprocess and Equinox.
2.3 Feature.xml: Bundles Aggregated#
A Feature.xml is an OSGi artifact (defined by the p2 Provisioning Platform) that aggregates multiple bundles, fragments, or other features into a single installable unit. Features simplify dependency management and versioning.
Key roles of Feature.xml:
- Declare a set of bundles to install (with versions and locations).
- Define dependencies on other features or bundles.
- Enable atomic installation/uninstallation of related components.
Setting Up the Equinox Environment#
Let’s start by setting up a minimal Equinox runtime to test our workflows.
Step 1: Download Equinox#
- Download the Equinox OSGi Framework from the Eclipse Downloads. Look for
org.eclipse.osgi_<version>.jar(the core Equinox bundle).
Step 2: Start Equinox#
Create a runtime directory and place org.eclipse.osgi_<version>.jar inside it. Open a terminal and run:
java -jar org.eclipse.osgi_<version>.jar -console This starts Equinox with a Felix Gogo shell (a command-line interface for managing bundles). You’ll see output like:
osgi>
Verify the initial bundles with ss (short status):
osgi> ss
Framework is launched.
id State Bundle
0 ACTIVE org.eclipse.osgi_3.18.300.v20221108-0830
Step 3: Test with a "Hello OSGi" Bundle#
To confirm the setup, create a simple bundle:
-
Create a
HelloOSGiproject with asrc/main/javadirectory. -
Add a
MANIFEST.MFinMETA-INF/:Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Hello OSGi Bundle-SymbolicName: com.example.helloosgi Bundle-Version: 1.0.0 Bundle-Activator: com.example.HelloActivator Import-Package: org.osgi.framework -
Create
com.example.HelloActivator:package com.example; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; public class HelloActivator implements BundleActivator { public void start(BundleContext context) { System.out.println("Hello, OSGi!"); } public void stop(BundleContext context) { System.out.println("Goodbye, OSGi!"); } } -
Package the bundle as a JAR (e.g.,
hello-osgi-1.0.0.jar) and place it inruntime/plugins/. -
In the Equinox console, install and start the bundle:
osgi> install file:plugins/hello-osgi-1.0.0.jar Bundle id is 1 osgi> start 1 Hello, OSGi!
If you see "Hello, OSGi!", your Equinox setup is working.
Dynamic Bundle Installation with Feature.xml#
Now, let’s explore how to use Feature.xml to install bundles dynamically in Equinox.
4.1 Anatomy of a Feature.xml#
A Feature.xml file has a standard structure defined by the p2 provisioning model. Here’s a minimal example:
<?xml version="1.0" encoding="UTF-8"?>
<feature
id="com.example.myfeature"
label="My Example Feature"
version="1.0.0"
provider-name="Example Inc.">
<!-- Bundles to include in the feature -->
<bundle
id="com.example.bundle1"
version="1.0.0"
location="plugins/com.example.bundle1_1.0.0.jar"/>
<bundle
id="com.example.bundle2"
version="2.0.0"
location="plugins/com.example.bundle2_2.0.0.jar"/>
<!-- Dependencies on other features -->
<requires>
<import feature="org.eclipse.equinox.core.feature" version="1.0.0"/>
</requires>
</feature> Key elements:
id: Unique feature identifier (e.g.,com.example.myfeature).version: Semantic version (major.minor.micro).bundle: Declares a bundle to install, withid(symbolic name),version, andlocation(path/URL to the JAR).requires: Dependencies on other features or bundles.
4.2 Creating a Feature.xml#
To create a feature:
-
Use Eclipse PDE (recommended):
- New > Feature Project.
- Enter feature ID, version, and select bundles to include.
- PDE auto-generates
Feature.xml.
-
Manual creation:
- Use the structure above, ensuring bundle paths are correct (relative to the feature’s location or absolute URLs).
4.3 Installing Features Dynamically with Equinox APIs#
Equinox provides the FeatureInstaller API (part of org.eclipse.equinox.p2.core) to install features programmatically. Here’s how to use it in a bundle:
Step 1: Add Dependencies#
Update your bundle’s MANIFEST.MF to import p2 packages:
Import-Package:
org.eclipse.equinox.p2.core,
org.eclipse.equinox.p2.operations,
org.eclipse.equinox.p2.metadata,
org.eclipse.equinox.p2.repository Step 2: Install a Feature Programmatically#
Create a class to handle feature installation:
package com.example.featureinstaller;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.operations.InstallOperation;
import org.eclipse.equinox.p2.operations.ProvisioningJob;
import org.eclipse.equinox.p2.operations.ProvisioningSession;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.IUType;
import org.eclipse.equinox.p2.query.QueryUtil;
import org.eclipse.equinox.p2.repository.IRepositoryManager;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import java.net.URI;
import java.util.Collection;
public class FeatureInstaller {
private final BundleContext context;
public FeatureInstaller(BundleContext context) {
this.context = context;
}
public void installFeature(URI featureRepoURI, String featureId, String featureVersion) throws Exception {
// Get the Provisioning Agent (p2's core service)
ServiceReference<IProvisioningAgent> agentRef = context.getServiceReference(IProvisioningAgent.class);
IProvisioningAgent agent = context.getService(agentRef);
try {
// Load the metadata repository containing the feature
IMetadataRepository repo = (IMetadataRepository) agent.getService(IRepositoryManager.SERVICE_NAME);
repo.load(featureRepoURI, null);
// Query for the feature (IInstallableUnit of type FEATURE)
Collection<IInstallableUnit> ius = repo.query(
QueryUtil.createIUQuery(featureId, featureVersion), null).toUnmodifiableSet();
if (ius.isEmpty()) {
throw new Exception("Feature not found: " + featureId + "/" + featureVersion);
}
// Create an install operation
ProvisioningSession session = new ProvisioningSession(agent);
InstallOperation op = new InstallOperation(session, ius);
ProvisioningJob job = op.getProvisioningJob(null);
// Run the job (install the feature)
job.schedule();
job.join(); // Wait for completion
if (job.getResult().isOK()) {
System.out.println("Feature installed successfully!");
} else {
throw new Exception("Feature installation failed: " + job.getResult().getMessage());
}
} finally {
context.ungetService(agentRef);
}
}
} Step 3: Trigger Installation from a Bundle#
Activate the installation in your bundle’s activator:
public class FeatureInstallerActivator implements BundleActivator {
private FeatureInstaller installer;
public void start(BundleContext context) throws Exception {
installer = new FeatureInstaller(context);
// Install a feature from a local repository
URI repoURI = new URI("file:/path/to/your/feature/repo/");
installer.installFeature(repoURI, "com.example.myfeature", "1.0.0");
}
public void stop(BundleContext context) {
// Cleanup
}
} Step 4: Test the Installation#
- Package your feature into a p2 repository (use Eclipse PDE: Export > Deployable Features > Select repository location).
- Update
repoURIinFeatureInstallerActivatorto point to your p2 repo. - Install and start the bundle in Equinox. The feature (and its bundles) will be installed dynamically.
Running Java Subprocesses in Equinox#
Now, let’s explore how to spawn and manage Java subprocesses from an Equinox bundle.
5.1 Executing Subprocesses: ProcessBuilder vs. Runtime.exec#
Java provides two APIs for spawning subprocesses: ProcessBuilder (preferred) and Runtime.exec(). ProcessBuilder offers more control over environment variables and I/O redirection.
5.2 Handling Classpath and Environment#
Subprocesses run in a separate JVM and do not inherit Equinox’s classpath. To include Equinox bundles in the subprocess’s classpath:
- Use the Equinox launcher JAR (
org.eclipse.equinox.launcher_<version>.jar) to start the subprocess with an OSGi runtime. - Explicitly set the
java.class.pathenvironment variable in the subprocess.
5.3 Subprocess Lifecycle Management#
Always handle:
- Input/Output Streams: Subprocesses block if their
stdout/stderrbuffers fill up. Use threads to read them. - Termination: Stop the subprocess when the parent bundle stops (to avoid zombies).
- Error Handling: Catch
IOExceptionand handle subprocess failures.
Example: Subprocess Execution Bundle#
Here’s a bundle that runs a Java subprocess and captures its output:
package com.example.subprocess;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class SubprocessActivator implements BundleActivator {
private Process subprocess;
@Override
public void start(BundleContext context) throws Exception {
// Start a subprocess: java -cp myapp.jar com.example.SubprocessMain
ProcessBuilder pb = new ProcessBuilder(
"java",
"-cp", "/path/to/subprocess-app.jar",
"com.example.SubprocessMain"
);
// Redirect error stream to stdout (optional)
pb.redirectErrorStream(true);
try {
subprocess = pb.start();
System.out.println("Subprocess started with PID: " + getPid(subprocess));
// Read subprocess output in a thread
new Thread(() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(subprocess.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Subprocess output: " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void stop(BundleContext context) throws Exception {
// Terminate the subprocess when the bundle stops
if (subprocess != null && subprocess.isAlive()) {
subprocess.destroy();
System.out.println("Subprocess terminated.");
}
}
// Get subprocess PID (Java 9+)
private long getPid(Process p) {
return p.pid(); // Requires Java 9+
}
} Integration: Subprocesses Triggering Dynamic Bundle Installation#
Now, let’s combine subprocesses and dynamic feature installation. We’ll build a workflow where:
- A subprocess generates a new OSGi bundle (e.g., from user input or data processing).
- The subprocess notifies Equinox that the bundle is ready.
- Equinox installs the feature containing the new bundle.
6.1 Use Case: Subprocess-Generated Bundles#
Imagine a code generator running in a subprocess that:
- Takes a JSON schema as input.
- Generates a Java model bundle (e.g.,
com.example.generated.model). - Writes the bundle to a directory monitored by Equinox.
6.2 Inter-Process Communication (IPC)#
To coordinate between the subprocess and Equinox, use:
- File Polling: Equinox watches a "flag file" created by the subprocess when the bundle is ready.
- Sockets: The subprocess sends a message to Equinox over a local socket.
- OSGi Remote Services: Advanced, but overkill for simple cases.
6.3 End-to-End Example#
Step 1: Subprocess Code Generator#
Create a simple subprocess that generates a bundle JAR and writes a flag file:
// SubprocessMain.java (runs in a separate JVM)
package com.example.subprocess;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class SubprocessMain {
public static void main(String[] args) throws IOException {
// Step 1: Generate a bundle JAR (simplified: create a dummy JAR)
File bundleFile = new File("/equinox/runtime/dropins/generated-bundle.jar");
bundleFile.createNewFile(); // In reality, use a JAR builder like Apache Maven Archiver
// Step 2: Write a flag file to signal completion
File flagFile = new File("/equinox/runtime/flags/bundle-ready.flag");
try (FileWriter fw = new FileWriter(flagFile)) {
fw.write("com.example.generated.model:1.0.0"); // Feature ID:version
}
System.out.println("Subprocess: Bundle generated and flag file created.");
}
} Step 2: Equinox Bundle to Monitor Flag Files#
Create an Equinox bundle that watches the flags/ directory and installs the feature when bundle-ready.flag is detected:
package com.example.monitor;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
import java.nio.file.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FlagMonitor implements Runnable {
private final BundleContext context;
private final Path flagDir = Paths.get("/equinox/runtime/flags/");
public FlagMonitor(BundleContext context) {
this.context = context;
}
@Override
public void run() {
try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
flagDir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE);
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
Path fileName = (Path) event.context();
if (fileName.toString().equals("bundle-ready.flag")) {
System.out.println("Flag file detected: " + fileName);
installGeneratedFeature();
}
}
}
key.reset();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void installGeneratedFeature() throws Exception {
// Install the feature containing the generated bundle
FeatureInstaller installer = new FeatureInstaller(context);
URI repoURI = new URI("file:/equinox/runtime/generated-features/"); // Contains the new feature
installer.installFeature(repoURI, "com.example.generated.feature", "1.0.0");
}
} Step 3: Activate the Monitor#
Start the flag monitor in your bundle’s activator:
public class MonitorActivator implements BundleActivator {
private ExecutorService executor;
public void start(BundleContext context) throws Exception {
executor = Executors.newSingleThreadExecutor();
executor.submit(new FlagMonitor(context));
System.out.println("Flag monitor started.");
}
public void stop(BundleContext context) throws Exception {
executor.shutdown();
}
} 6.4 Testing the Workflow#
- Run the Equinox runtime with the monitor bundle.
- Execute the subprocess code generator.
- The subprocess generates the bundle and flag file.
- The monitor detects the flag, triggers feature installation.
- Verify the new bundle is active with
ssin the Equinox console.
Troubleshooting Common Issues#
Bundle Resolution Errors#
- Cause: Missing dependencies in
Feature.xmlorMANIFEST.MF. - Fix: Use
osgi> diag <bundle-id>to check unresolved dependencies. Add required bundles to the feature.
Subprocess Classpath Issues#
- Cause: Subprocess can’t find classes from Equinox bundles.
- Fix: Explicitly set the subprocess classpath to include Equinox JARs (e.g.,
org.eclipse.osgi.jar).
Feature Installation Fails Silently#
- Cause: Incorrect repository URI or missing p2 dependencies in the bundle.
- Fix: Enable p2 debug logging:
-Dorg.eclipse.equinox.p2.core.debug=true. Check logs for repository load errors.
Subprocess Hangs#
- Cause: Not reading
stdout/stderrfrom the subprocess. - Fix: Always read subprocess streams in separate threads (as shown in Section 5.3).
Conclusion#
Running Java subprocesses and dynamically installing features in Equinox unlocks powerful modular workflows. By isolating tasks in subprocesses and extending applications with dynamic bundles, you can build flexible, scalable OSGi systems that adapt to changing requirements without restarts.
Key takeaways:
- Use
Feature.xmlto package and version bundles for easy installation. - Leverage Equinox’s p2 APIs for programmatic feature installation.
- Isolate subprocesses to avoid disrupting the main Equinox runtime.
- Use IPC (e.g., file polling) to coordinate between subprocesses and Equinox.