I’m facing an issue with my Java Spring Boot application where I dynamically load plugins using ServiceLoader. The application works perfectly in my local development environment (IntelliJ IDE), but when I build the JAR files and run them, I encounter the following error:
Caused by: java.util.ServiceConfigurationError: com.example.plugins.Plugin: com.example.plugins.vendor.VendorPluginManager not a subtype
at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:593)
at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNextService(ServiceLoader.java:1244)
at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:1273)
at java.base/java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1309)
at java.base/java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1393)
at com.example.global.plugins.PluginLoader.loadPlugins(PluginLoader.java:39)
at com.example.global.plugins.PluginConfig.plugins(PluginConfig.java:22)
at com.example.global.plugins.PluginConfig$$SpringCGLIB$$0.CGLIB$plugins$0(<generated>)
at com.example.global.plugins.PluginConfig$$SpringCGLIB$$FastClass$$1.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
at com.example.global.plugins.PluginConfig$$SpringCGLIB$$0.plugins(<generated>)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:139)
... 56 common frames omitted
Project Structure
server (spring-boot)
├── src
├── build.gradle
├── plugin-module
│ ├── build.gradle
│ ├── src
└── common-plugin
├── build.gradle
├── src
plugins
└── plugin-module-1.0.0.jar (built-plugin file)
Common Plugin Interface
In common-plugin:
package com.example.plugins;
public interface Plugin {
void initialize();
String getName();
}
Plugin Implementation
In plugin-module:
package com.example.plugins.vendor;
import com.example.plugins.Plugin;
public class VendorPluginManager implements Plugin {
@Override
public void initialize() {
System.out.println("VendorPluginManager initialized!");
}
@Override
public String getName() {
return "vendorPlugin";
}
// Additional methods...
}
META-INF/services/com.example.plugins.Plugin
com.example.plugins.vendor.VendorPluginManager
PluginLoader
In server:
package com.example.global.plugins;
import com.example.plugins.Plugin;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
@Component
public class PluginLoader {
@Value("${plugins.path:./plugins}")
private String pluginsPath;
public List<Plugin> loadPlugins() throws Exception {
File dir = new File(pluginsPath);
File[] files = dir.listFiles((d, name) -> name.endsWith(".jar"));
List<Plugin> plugins = new ArrayList<>();
if (files != null) {
for (File file : files) {
URL[] urls = {file.toURI().toURL()};
try (URLClassLoader classLoader = new URLClassLoader(urls)) {
ServiceLoader<Plugin> loader = ServiceLoader.load(Plugin.class, classLoader);
for (Plugin plugin : loader) {
plugins.add(plugin);
}
}
}
}
return plugins;
}
}
Gradle Build Files
server/build.gradle:
plugins {
id 'java'
id 'org.springframework.boot' version '2.6.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
}
group = 'com.example'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
implementation project(':common-plugin')
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
bootJar {
mainClassName = 'com.example.Application'
}
plugin-module/build.gradle:
plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '7.1.2'
}
group = 'com.example'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
compileOnly project(':common-plugin')
implementation 'com.some.library:example:1.0.0'
}
Issue
It seems like the VendorPluginManager class is not being recognized as a subtype of Plugin when I build the JAR files and run them.
Why am I getting this ServiceConfigurationError in deployment?
What steps can I take to resolve this issue?
Any help or suggestions would be greatly appreciated!