PF4J: a Java plugin framework that skips XML and OSGi
Plugin Framework for Java (PF4J)
At a glance
- What is it?
- PF4J is a roughly 100 KB Apache-2.0 plugin microframework for Java whose only dependency is slf4j-api. It suits teams that want runtime extension points without an OSGi container, and it falls short when plugins must be isolated at the JVM level.
- Who is it for?
- Adopt PF4J when a monolithic Java application needs third-party extensions loaded from jars at runtime and you want to avoid XML descriptors and an OSGi container. Do not adopt it when plugins must be isolated at the JVM level or when you need a governance layer with signing, sandboxing and remote updates, because PF4J's core is a class-loader based loader and the README documents no such features.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem PF4J solves, and who actually needs it
A Java application that wants third parties to add behaviour has three options: recompile with their code, load jars by hand with a URLClassLoader, or adopt a plugin container. PF4J targets the third option while trying to stay small. The README describes it as a microframework that transforms a monolithic Java application into a modular one, and the stated reasons to pick it are that it is around 100 KB, depends only on slf4j-api, and needs no XML configuration.
The audience is narrow and specific. If you are building a platform where customers, partners or internal teams ship separate jars that must be discovered, started, stopped and deleted at runtime, PF4J gives you the plumbing. The README's list of adopters includes Netflix Spinnaker, Facebook Buck (Java version), Apache Tika, Apache BifroMQ, Dremio, MuleSoft Anypoint Code Builder and Eclipse ECSP. That list is useful as a signal of the kind of system PF4J fits: long-lived platforms with a plugin surface, not short-lived applications.
If your extension points are known at compile time and shipped inside your own artifact, PF4J adds a lifecycle and a descriptor format you will not use. The README notes that since version 0.9 you can define an extension directly in the application jar, so a default or system extension does not require a plugin, but that is a convenience, not a reason to adopt the framework.
How PF4J loads plugins: class loaders, descriptors and extension points
The core mechanism is documented in the Components section. A Plugin is the base class for all plugin types, and each plugin is loaded into a separate class loader to avoid conflicts. A PluginManager handles loading, starting and stopping. Built-in implementations are JarPluginManager, ZipPluginManager and DefaultPluginManager, which the README describes as JarPluginManager plus ZipPluginManager. A custom manager can be built from AbstractPluginManager by implementing only the factory methods. A PluginLoader loads all the classes a plugin needs.
The extension model is annotation driven. An extension point is any Java interface or abstract class marked with the ExtensionPoint marker interface. An extension is a class annotated with @Extension. PF4J's own summary is blunt: no XML, only Java. The README also states that a plugin can define extension points itself, so plugins are not only consumers of the host's API.
The descriptor is where the framework's flexibility becomes visible. PluginDescriptorFinder and ExtensionFinder are named as extension seams, which means the default manifest-based descriptor is replaceable. The trade-off is that the framework deliberately keeps this layer thin, and everything a production plugin system usually needs on top (signing, sandboxing, remote updates) sits outside the core.
A first PF4J plugin: extension point, extension, and the manifest
The README's usage section is short enough to follow directly. Start by declaring an extension point in the application as an interface that extends the ExtensionPoint marker:
public interface Greeting extends ExtensionPoint {
String getGreeting();
}Any class that implements it and carries @Extension is picked up as an extension. The README's example returns a fixed string:
@Extension
public class WelcomeGreeting implements Greeting {
public String getGreeting() {
return "Welcome";
}
}A plugin class is optional. The README says the Plugin-Class property in the descriptor is optional, and you only need it if you care about lifecycle hooks. When you do provide one, you override start, stop and delete:
public class WelcomePlugin extends Plugin {
@Override
public void start() { System.out.println("WelcomePlugin.start()"); }
}The simplest distribution is a jar with plugin metadata in MANIFEST.MF. The README's example manifest carries Plugin-Class, Plugin-Dependencies, Plugin-Id, Plugin-Provider and Plugin-Version. Plugin-Id and Plugin-Version are described as mandatory; Plugin-Class and Plugin-Dependencies as optional. On the host side, the README's main method creates a JarPluginManager and calls loadPlugins:
PluginManager pluginManager = new JarPluginManager();
pluginManager.loadPlugins();For a runnable end-to-end sample, the repository ships demo/ with maven and gradle variants, plus a run-demo.sh and run-demo.bat at the root. The demo/README.md is the place to check the exact commands for your build tool, since the top-level README does not spell them out.
Where PF4J stops: isolation, lifecycle and the missing operations layer
The class-loader-per-plugin design is the framework's main selling point and its main limit. Separate class loaders reduce conflicts between plugins, but they do not isolate a plugin from the host JVM. A plugin runs with the same permissions, the same memory and the same failure domain as the application. The README does not document a security manager model, a permission policy, or a sandbox. If you need to run untrusted third-party code, PF4J's core gives you no answer to that, and you should treat it as the wrong tool rather than as a starting point you can harden later.
The README's own framing is that PF4J is an alternative to OSGi that is easy to learn and implement. That is an honest description of the trade-off. OSGi gives you a module system with versioned package imports and exports; PF4J gives you a loader and an extension registry. If two plugins need different versions of the same library and both versions must be visible to the host, PF4J's model will not resolve that for you.
Operationally, the framework also stops short of what a plugin marketplace needs. The README mentions that ReportPortal built a plugin marketplace on pf4j-update, which is a separate project in the ecosystem, not part of the core. The README does not document rollback, plugin signing, or a remote repository protocol. Those are things you would have to add or find elsewhere.
PF4J compared with Spring's plugin support and with OSGi
The two comparisons that matter are OSGi and Spring-based plugin wiring. Against OSGi, the difference is architectural rather than cosmetic. OSGi is a runtime module system: bundles declare imports and exports, and the container resolves versions and wires them. PF4J is a plugin loader: it reads a descriptor, creates a class loader, and registers extensions. The README positions PF4J explicitly as an alternative to OSGi on the grounds of learnability, and the size claim (around 100 KB, only slf4j-api) makes that concrete. The cost is that resolution, versioning and service dynamics are yours to handle.
Against Spring, the difference is where the container lives. PF4J's core has no dependency on Spring and no XML. Its extension points are plain interfaces marked with ExtensionPoint, and its extensions are classes annotated with @Extension. Spring-based plugin approaches typically build on the Spring context and its bean lifecycle, which means the plugin's wiring is expressed in the same annotations and configuration the rest of the application uses. Neither approach is a superset of the other: PF4J keeps the framework out of your application's dependency graph, while a Spring-based approach gives you the rest of Spring's machinery for free if you are already there. The README does not describe a Spring integration in the core repository; the ecosystem around PF4J is where that lives.
Maintenance, licence and what an upgrade costs
The repository is not archived, and the last push to master was on 2026-09-20. The release history shows release-3.15.0 on 2026-01-27, release-3.15.1 on 2026-08-25 and release-3.16.0 on 2026-09-20, so the project ships patch and minor releases rather than sitting still. The CHANGELOG.md at the repository root is the file to read before upgrading, since the README does not carry a migration guide.
The licence is Apache-2.0, which is permissive and includes an explicit patent grant. That matters for a plugin framework because adopters often redistribute the framework inside a larger product. Apache-2.0 does not impose copyleft on your own code, but it does require that you preserve the licence and notices, and it does not grant trademark rights. This is not legal advice; check the LICENSE file and your own distribution obligations.
The upgrade cost is tied to the extension seams. Because PluginDescriptorFinder and ExtensionFinder are replaceable, a custom finder is the piece most likely to break when descriptor handling changes. If you rely on the default MANIFEST.MF descriptor, the surface you depend on is small: Plugin-Id and Plugin-Version are mandatory, Plugin-Class and Plugin-Dependencies are optional. Keep your descriptor generation in one place in your build so a version bump is a one-file change.
Editorial conclusion
Adopt PF4J when a monolithic Java application needs third-party extensions loaded from jars at runtime and you want to avoid XML descriptors and an OSGi container. Do not adopt it when plugins must be isolated at the JVM level or when you need a governance layer with signing, sandboxing and remote updates, because PF4J's core is a class-loader based loader and the README documents no such features. Verify first the exact manifest attributes your build writes (Plugin-Id and Plugin-Version are mandatory), whether your packaging step actually produces the zip layout ZipPluginManager expects, and whether the demo/ modules match your build tool. The last push to master was on 2026-09-20, and the same day carries release-3.16.0.
Frequently asked questions
What is the main purpose of a plugin in PF4J?
In PF4J a plugin is a container for extension points and extensions plus lifecycle methods for start, stop and delete. It is loaded into a separate class loader so that third parties can extend an application without recompiling it.
What does "plugin" mean in Java when using PF4J?
PF4J treats a plugin as similar to a module in other systems. If you do not need the lifecycle hooks, you are not forced to supply a plugin class, because the Plugin-Class property in the descriptor is optional; you still need an id and a version for tracking.
How do I add PF4J to a Maven or Gradle build?
The README points to the Maven Central badge for the org.pf4j:pf4j coordinates and the repository ships demo/maven and demo/gradle modules. The top-level README does not print the dependency snippet, so read demo/README.md for the exact build file contents.
Which PluginManager implementation should I use in PF4J?
The README lists JarPluginManager, ZipPluginManager and DefaultPluginManager, where DefaultPluginManager is described as JarPluginManager plus ZipPluginManager. You can also implement a custom manager from AbstractPluginManager by implementing only the factory methods.
Does PF4J require XML configuration?
No. The README states that PF4J uses no XML, only Java: you mark an interface or abstract class with the ExtensionPoint marker and annotate an implementation with @Extension.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pf4j-pf4j)