What if your Java app could load new code while it keeps breathing?
That is the daily promise of Apache Felix and OSGi. We keep chasing modular code, faster deploys, and fewer production facepalms. Sun is now Oracle, app servers feel heavy, and the cloud buzz is loud. In the middle of that noise, OSGi gives a calm idea that works in practice. Split your app into bundles, wire them through the service registry, and swap pieces without bringing the house down. Felix is a lean runtime that makes this concrete, with a simple console and a set of well shaped tools you can explain to your team in one coffee.
Felix keeps things small and honest.
Eclipse Equinox is great and ships in a lot of places, and Knopflerfish has fans too. Felix sits in a sweet spot: small download, friendly shell, very readable code, and a lively Apache vibe. If you want to sprinkle OSGi into an existing app or ship a headless service, Felix feels natural. You can start it from plain Java, embed it inside your app, or run it as its own process. The goal is simple: build modules that hide their guts, expose clear services, and stop fighting classloader weirdness in the dark.
You get a runtime you can reason about.
Why Felix for OSGi today
We all want faster cycles. With OSGi on Felix, you can update one feature by pushing one bundle. No giant ear, no full restart. The service registry encourages decoupling by default. You code against an interface, register an implementation as a service, and find it at runtime with zero ceremony. Your web features, job schedulers, cache adapters, even payment connectors can live as separate bundles with their own versions. The pay off is not just reloads. It is how your code reads after six months.
It is modularity you can touch.
From zero to a running Felix
Download Felix from the Apache site, unzip it, and launch the main class. No magic. If you do not want the jar switch on the command line, call the main class directly. You will land in a console that lets you list bundles, install new ones, start and stop them, and peek into wiring. The first impression matters, and this one says simple and focused.
# Start Felix by class name to avoid jar flags
java -cp bin/felix.jar org.apache.felix.main.Main
# In the shell try
lb
helpYou now have a living module system in your terminal.
Your first bundle service
Let us write a tiny service and register it. Keep the public surface small. Put the interface in one bundle and the implementation in another. Yes, two jars, and that is the point. The interface bundle becomes a stable contract while the impl bundle can move fast.
// bundle: hello.api
package com.example.hello.api;
public interface HelloService {
String greet(String name);
}Now the implementation bundle with a BundleActivator that registers the service.
// bundle: hello.impl
package com.example.hello.impl;
import com.example.hello.api.HelloService;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
public class Activator implements BundleActivator {
private HelloService service;
@Override
public void start(BundleContext context) {
service = new HelloService() {
public String greet(String name) {
return "Hi " + name + " from Felix";
}
};
context.registerService(HelloService.class.getName(), service, null);
System.out.println("HelloService registered");
}
@Override
public void stop(BundleContext context) {
System.out.println("HelloService stopped");
}
}Build both jars, make sure the impl imports the api package, drop them into the Felix load directory or install them from the console, and start them. You can now look up the service from another bundle and call it. No reflection circus. Just a registry and a contract.
Declarative Services that feel natural
Writing an activator is fine for learning. For real apps, Declarative Services keeps your code clean. With Felix SCR you can use annotations and skip the XML dance. Mark your class as a component and a service, add references for what you consume, and let the runtime manage lifecycle.
// bundle: hello.impl.ds
package com.example.hello.impl;
import com.example.hello.api.HelloService;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
@Component(immediate = true)
@Service(value = HelloService.class)
public class HelloServiceImpl implements HelloService {
public String greet(String name) {
return "Hello " + name + " with DS";
}
}With DS you get activation and deactivation hooks when needed, and services appear only when their dependencies are present. Your code reads like plain Java and the runtime does the wiring.
Working the Felix console
The shell is where Felix shines in day to day work. Install a built jar from a file path or a URL. Start it. Stop it. Update it. List all bundles and see states. The command names are short on purpose. Pair this with a file watcher that calls update on change and you get quick feedback without rebuilding the world.
# List bundles
lb
# Install from a local path
install file:hello.api.jar
install file:hello.impl.jar
# Start by bundle id
start 7
start 8
# Update and restart in place
update 8
stop 8
start 8Small moves add up to fast loops.
Class loading gotchas and versioning
OSGi gives you strong boundaries, and that also means you must be clear with what you export and import. Keep your exported packages stable and version them. Keep your imports as ranges. Watch out for splitting a package across bundles. That is a trap. Place each public package in one bundle, version it, and describe a sensible range in the consumers. Also avoid static singletons that reach across bundles. Use services for that. Services are the friendly way to share. Static state is the gremlin that breaks reloads.
Design for change and your runtime will thank you.
Build and packaging tips
You can build bundles with bnd, with Ant tasks, or with Maven. If you go Maven, look for the maven bundle plugin and the Felix SCR plugin. They add the right headers and generate DS metadata from annotations. Pick one path and keep it boring. The trick is to make bundle metadata part of the code review, not a mystery file nobody touches. Agree on version rules for packages and services and stick to them. Your future self will be grateful.
Automate the boring parts, keep the smart parts human.
When you might pick Equinox instead
If you are deep into the Eclipse world or need tight ties with PDE and buddies, Equinox is a strong choice. Tooling around Eclipse RCP is mature and plenty of teams ship desktop apps that way. For server and headless work where you want a tiny core and you plan to script your own path, Felix feels lighter. Both follow the same OSGi specs, so you can switch if you keep your code honest with standard APIs.
Choose the runtime that fits your team, not your neighbor.
Small bundles and clear services beat giant jars every single time.