apache/skywalking · error · IllegalArgumentException
Function {} can't be found by javaassist.
Error message
Function {} can't be found by javaassist. What it means
During metric creation MeterSystem resolves the function class in the Javassist ClassPool via pool.get(meterFunction.getCanonicalName()) to obtain the CtClass parent for the generated Metrics subclass. A javassist.NotFoundException means the pool's classpath does not contain the function class, even though the JVM classloader can see it. It is rethrown as this IllegalArgumentException with the 'can't be found by javaassist' wording.
Source
Thrown at oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/meter/MeterSystem.java:247
if (foundDataType) {
break;
}
}
}
if (!foundDataType) {
throw new IllegalArgumentException("Function " + functionName
+ " requires <" + acceptance + "> in AcceptableValue"
+ " but using " + dataType.getName() + " in the creation");
}
final CtClass parentClass;
try {
parentClass = pool.get(meterFunction.getCanonicalName());
if (!Metrics.class.isAssignableFrom(meterFunction)) {
throw new IllegalArgumentException(
"Function " + functionName + " doesn't inherit from Metrics.");
}
} catch (NotFoundException e) {
throw new IllegalArgumentException("Function " + functionName + " can't be found by javaassist.");
}
final String className = formatName(metricsName);
// Prototype-first short-circuit (fires on runtime FILTER_ONLY re-apply). Every
// runtime apply hands in a fresh {@code ClassPool}, so the pool-based existence
// check below cannot see a Metrics class the previous apply defined in a now-dead
// pool. Without this guard, every FILTER_ONLY update generated a new Metrics class,
// new MetricsStreamProcessor workers, and a new prototype that shadowed the old
// one in {@link #meterPrototypes} — a removeMetric by name could only tear down the
// latest generation, leaving prior workers + classloaders pinned forever. Match on
// scope + data type + function class; any of those differing is a genuine shape
// change and the existing IllegalArgumentException on the pool path fires below.
final MeterDefinition existingDefinition = meterPrototypes.get(metricsName);
if (existingDefinition != null
&& existingDefinition.getScopeType() == type
&& existingDefinition.getDataType().equals(dataType)
&& existingDefinition.getMeterPrototype().getClass().getSuperclass() == meterFunction) {
log.debug("Metric {} already registered with matching shape; reusing existing "
+ "Metrics class + workers (FILTER_ONLY re-apply path).", metricsName);View on GitHub (pinned to 102af09b4a)
Solutions
- Append the missing path to the pool before create(): ClassPool.getDefault().appendClassPath(new ClassClassPath(meterFunctionClass)) or append a JarClassPath for the plugin jar
- If running as a fat/shaded jar, ensure the function classes are unpacked or add a LoaderClassPath for the container classloader
- Verify the function jar is actually in oap-libs/ (or on the OAP classpath) and was loaded by the same loader that supplied the class to FunctionRegister
Example fix
// before ClassPool pool = ClassPool.getDefault(); meterSystem.create(name, func, type, Long.class, pool, neighbor); // -> NotFoundException: Function ... can't be found by javaassist. // after ClassPool pool = ClassPool.getDefault(); pool.appendClassPath(new org.javassist.ClassClassPath(functionClass)); meterSystem.create(name, func, type, Long.class, pool, neighbor);
Defensive patterns
Strategy: validation
Validate before calling
ClassPool pool = ClassPool.getDefault();
try {
pool.get(meterFunctionClass.getCanonicalName());
} catch (javassist.NotFoundException e) {
pool.appendClassPath(new org.javassist.ClassClassPath(meterFunctionClass));
}
meterSystem.create(name, functionName, type, dataType, pool, neighbor); Try / catch
try {
meterSystem.create(...);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("can't be found by javaassist")) {
pool.appendClassPath(new ClassClassPath(functionClass));
meterSystem.create(...); // retry once with fixed classpath
} else { throw e; }
} Prevention
- Append a ClassClassPath for every custom function class right after building any new ClassPool
- In fat-jar deployments, append a LoaderClassPath for the application classloader to the default pool
- Keep custom function jars in oap-libs so both the JVM loader and Javassist pools see them
When it happens
Trigger: A custom meter function jar sits on the JVM classpath but was never appended to the ClassPool with pool.appendClassPath / ClassPool.getDefault().appendClassPath; the pool-aware create(...) overload received a fresh pool built for a runtime rule that only includes the rule classloader's path, not the jar holding the function; OAP runs from a shaded/fat-jar layout where the function class lives in a nested jar the default pool cannot read.
Common situations: Deploying custom MAL function plugins into oap-libs without ensuring the meter-DSL runtime adds that path to its ClassPool; running OAP as a fat jar (java -jar) where ClassPool.getDefault() cannot resolve classes inside BOOT-INF; application-server classloader setups that hide the jar from Javassist's default ClassClassPath.
Related errors
- Function {} doesn't inherit from Metrics.
- Can not locate oap core jar file by path:{}
- pool must not be null
- classLoaderNeighbor must not be null
- {slot} expects a plain integer literal, got '{numText}' (suf
AI-assisted analysis of apache/skywalking@102af09b4a (2026-08-14).
Data as JSON: /api/errors/4336de409c0277e1.
Report an issue: GitHub.