Plugin, PluginRegistry, and PluginLoader for dynamic registration without modifying host code, a genuine Open/Closed application, plus the versioning and compatibility checks a real plugin system needs.
Published September 23, 2026
interface Plugin {
String name();
String version();
void onLoad(HostContext context); // the plugin's entry point into the host application
}
class PluginRegistry {
Map<String, Plugin> plugins = new ConcurrentHashMap<>();
void register(Plugin plugin) { plugins.put(plugin.name(), plugin); }
}
class PluginLoader {
PluginRegistry registry;
void loadFrom(File pluginJar) {
// uses a ServiceLoader or a custom ClassLoader to discover and instantiate
// classes implementing Plugin from the given JAR, without the host needing
// to know their concrete class names AHEAD OF TIME
}
}
The Plugin interface is the entire contract between host and extension — the host application only ever depends on THIS interface, never on any specific plugin implementation, which is the foundational decoupling that makes dynamic loading possible at all.
// Java's built-in ServiceLoader mechanism — discovers implementations declared in
// META-INF/services/com.example.Plugin, with ZERO host code changes needed to add one
ServiceLoader<Plugin> loader = ServiceLoader.load(Plugin.class);
for (Plugin plugin : loader) {
registry.register(plugin);
}
This is the most literal, concrete application of Open/Closed anywhere in this Machine Coding chapter: a NEW plugin is added by dropping a JAR file (declaring its implementation via a META-INF/services file, Java's standard ServiceLoader discovery mechanism) — the host application's OWN CODE is never touched, never recompiled, never redeployed, to support a new plugin. Every other Strategy/Observer-based exercise in this chapter still requires a code change (registering a new implementation somewhere) at compile time; a true plugin system removes even that.
class PluginCompatibilityChecker {
String hostApiVersion; // e.g. "2.x"
boolean isCompatible(Plugin plugin) {
return VersionRange.parse(plugin.requiredHostVersion()).contains(hostApiVersion);
}
}
A plugin built against an OLDER version of the host's API might call methods that no longer exist, or rely on behavior that's since changed — loading an incompatible plugin blindly risks a runtime crash, potentially well after the plugin appeared to load successfully. A compatibility check (the plugin declaring which host API version range it was built against, checked BEFORE fully activating it) is what turns a potential runtime crash into a clean, early rejection with a clear error — the same "fail fast and explicitly, rather than fail confusingly later" principle that shows up throughout this course's validation-focused lessons.
Q: What happens if two plugins register under the same name? A: This needs an explicit policy decision — reject the second registration outright (safest, avoids ambiguity about which plugin actually handles a given name), or allow explicit override with a warning (more flexible, but risks a plugin silently shadowing another without the operator realizing) — silently allowing the last-registered one to win with no warning at all is the one option that's never correct, since it hides a real conflict.
Q: How would you isolate a misbehaving plugin from crashing the entire host application?
A: Loading each plugin in its own CLASSLOADER (isolating its dependencies from the host's and from other plugins') plus wrapping onLoad() and any subsequent plugin calls in exception handling that logs and DISABLES that specific plugin rather than propagating the failure — a plugin architecture that lets one bad plugin take down the whole host defeats much of the point of isolating extensions in the first place.
Q: Does ServiceLoader-based discovery have any downsides compared to a more explicit plugin-registration API? A: ServiceLoader is simple and standard but offers limited control over LOAD ORDER and limited ability to pass CONFIGURATION to a plugin at discovery time — more sophisticated plugin systems (OSGi, for instance) provide richer lifecycle management (explicit start/stop, dependency resolution between plugins) at real added complexity cost, a trade-off worth naming explicitly rather than assuming ServiceLoader is always sufficient.
Q: How does HostContext (passed to onLoad) relate to the Dependency Inversion principle? A: HostContext is exactly the mechanism that lets a plugin access host capabilities (registering a new API route, subscribing to an event) WITHOUT the plugin needing a hard compile-time dependency on the host's concrete internal classes — the plugin depends only on HostContext's own interface, the same abstraction-not-concretion discipline behind Dependency Inversion applied to the plugin boundary specifically.