quarkusio/quarkus · error · UnsupportedOperationException
OSGi is not supported by quarkus-liquibase
Error message
OSGi is not supported by quarkus-liquibase
What it means
quarkus-liquibase substitutes Liquibase's liquibase.util.OsgiUtil.loadClass with a GraalVM @Substitute that always throws UnsupportedOperationException, because OSGi classloading is meaningless in a native Quarkus image. It only fires when Liquibase code running inside a native executable attempts to resolve a class through its OSGi path instead of the plain Class.forName path. This is intentionally unsupported behavior, not a bug.
Source
Thrown at extensions/liquibase/liquibase-common/src/main/java/io/quarkus/liquibase/common/runtime/graal/SubstituteOsgiUtil.java:11
package io.quarkus.liquibase.common.runtime.graal;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
@TargetClass(liquibase.util.OsgiUtil.class)
final class SubstituteOsgiUtil {
@Substitute
public static <T> Class<T> loadClass(String className) throws ClassNotFoundException {
throw new UnsupportedOperationException("OSGi is not supported by quarkus-liquibase");
}
}
View on GitHub (pinned to e1c734241f)
Solutions
- Verify the load happens only in native mode; test the same flow in JVM mode to isolate the OSGi substitute path
- Remove or restructure the custom Liquibase extension/class so it is discovered via standard ServiceLoader/Class.forName instead of OSGi (register it via META-INF/services or liquibase.locator ServiceLocator)
- Pin/upgrade liquibase version bundled by quarkus-liquibase so the OSGi code path is not taken
- If the class is truly needed, add your own GraalVM substitution/feature for liquibase.util.OsgiUtil or the calling code in your app
Defensive patterns
Strategy: try-catch
Validate before calling
// Native apps: ensure custom Liquibase classes are loaded via ServiceLoader before Liquibase runs
if (Class.forName("my.pkg.MyCustomChange", true, Thread.currentThread().getContextClassLoader()) == null) {
throw new IllegalStateException("Custom Liquibase class not on classpath; OsgiUtil path will fail in native");
} Type guard
boolean isNativeImage = System.getProperty("org.graalvm.nativeimage.iscompilation") != null
|| Class.forName("com.oracle.svm.core.annotate.Substitute", false, ClassLoader.getSystemClassLoader()) != null; Try / catch
try {
liquibase.update(context);
} catch (UnsupportedOperationException e) {
if (e.getMessage() != null && e.getMessage().contains("OSGi is not supported")) {
throw new IllegalStateException("Liquibase tried OSGi classloading in native mode; register classes via ServiceLoader", e);
}
throw e;
} Prevention
- Avoid custom Liquibase classes resolved through OSGi ServiceLocator in native builds
- Register Liquibase extensions in META-INF/services so plain Class.forName/ServiceLoader discovery is used
- Always test Liquibase flows in native-image before production
- Pin Liquibase versions known to work with quarkus-liquibase native substitution
When it happens
Trigger: Running a Quarkus application in native-image mode where Liquibase calls OsgiUtil.loadClass(className) — typically during changelog parsing/custom class resolution (e.g. custom ChangeLogParser, ChangeGenerator, or extension classes registered only via OSGi service discovery) in a native build.
Common situations: Users shipping Liquibase extensions or custom classes (CustomChangeWrapper, custom task/changes) that Liquibase tries to load via the OSGi branch in native mode; unusual Liquibase versions where class lookup prefers the OSGi path; native-image builds where no substitute for the actual loading path exists.
Related errors
- Cannot parse version from output: ${stringOutput}
- Not Implemented in native mode
- Unable to create new instance for ${clazz}
- .pfa font files are not supported. Use TrueType fonts, i.e.
- .pfb font files are not supported. Use TrueType fonts, i.e.
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/e23d55d167a85315.
Report an issue: GitHub.