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

  1. Append the missing path to the pool before create(): ClassPool.getDefault().appendClassPath(new ClassClassPath(meterFunctionClass)) or append a JarClassPath for the plugin jar
  2. If running as a fat/shaded jar, ensure the function classes are unpacked or add a LoaderClassPath for the container classloader
  3. 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

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


AI-assisted analysis of apache/skywalking@102af09b4a (2026-08-14). Data as JSON: /api/errors/4336de409c0277e1. Report an issue: GitHub.