You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/02/21 11:58:27 UTC

(skywalking-graalvm-distro) 01/02: MAL-to-Java transpiler: compile Groovy DSL expressions to pure Java at build time

This is an automated email from the ASF dual-hosted git repository.

wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-graalvm-distro.git

commit e26cc15d238466ce44f95e586151cccc721620a2
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Sat Feb 21 19:38:33 2026 +0800

    MAL-to-Java transpiler: compile Groovy DSL expressions to pure Java at build time
    
    Transpile all 1250 MAL expressions and 29 filter closures from Groovy DSL
    to pure Java classes implementing MalExpression/MalFilter interfaces. This
    eliminates Groovy runtime dependency for MAL evaluation in GraalVM native image.
    
    Precompiler changes:
    - MalToJavaTranspiler: Groovy AST → Java source transpiler (67 unit tests)
    - DSL.java: added EXPRESSION_REGISTRY to capture expression strings
    - Precompiler.transpileMAL(): wired into build pipeline after Groovy compilation
    - Same-FQCN stubs (MalExpression, MalFilter, SampleFamilyFunctions) for javac
    
    meter-analyzer-for-graalvm changes:
    - ExpressionParsingContext: same-FQCN with public get()/create() for cross-package access
    - SampleFamily: tagNotEqual/tagMatch/tagNotMatch changed to varargs, newValue made public
    - FilterExpression: wrapped Closure in SampleFilter lambda for GraalVM SampleFamily API
    - MalExpression, MalFilter, SampleFamilyFunctions: Java functional interface definitions
    - pom.xml: added ExpressionParsingContext to shade excludes
---
 .../skywalking/oap/meter/analyzer/dsl/DSL.java     |    8 +
 .../oap/meter/analyzer/dsl/MalExpression.java      |   43 +
 .../oap/meter/analyzer/dsl/MalFilter.java          |   42 +
 .../meter/analyzer/dsl/SampleFamilyFunctions.java  |   78 ++
 .../precompiler/MalToJavaTranspiler.java           | 1232 ++++++++++++++++++++
 .../server/buildtools/precompiler/Precompiler.java |  325 ++++++
 .../precompiler/MalToJavaTranspilerTest.java       |  982 ++++++++++++++++
 .../meter-analyzer-for-graalvm/pom.xml             |    5 +
 .../analyzer/dsl/ExpressionParsingContext.java     |  100 ++
 .../oap/meter/analyzer/dsl/FilterExpression.java   |    2 +-
 .../oap/meter/analyzer/dsl/MalExpression.java      |   40 +
 .../oap/meter/analyzer/dsl/MalFilter.java          |   39 +
 .../oap/meter/analyzer/dsl/SampleFamily.java       |  913 +++++++++++++++
 .../meter/analyzer/dsl/SampleFamilyFunctions.java  |   75 ++
 14 files changed, 3883 insertions(+), 1 deletion(-)

diff --git a/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/DSL.java b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/DSL.java
index f6e622e..930112f 100644
--- a/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/DSL.java
+++ b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/DSL.java
@@ -56,6 +56,7 @@ public final class DSL {
     private static final Map<String, String> SCRIPT_REGISTRY = new LinkedHashMap<>();
     private static final Map<String, Integer> METRIC_OCCURRENCE = new HashMap<>();
     private static final Map<String, String> EXPRESSION_HASHES = new LinkedHashMap<>();
+    private static final Map<String, String> EXPRESSION_REGISTRY = new LinkedHashMap<>();
 
     public static void setTargetDirectory(File dir) {
         TARGET_DIRECTORY = dir;
@@ -69,11 +70,17 @@ public final class DSL {
         return Collections.unmodifiableMap(EXPRESSION_HASHES);
     }
 
+    /** Returns scriptName → expression string mapping for transpiler. */
+    public static Map<String, String> getExpressionRegistry() {
+        return Collections.unmodifiableMap(EXPRESSION_REGISTRY);
+    }
+
     public static void reset() {
         TARGET_DIRECTORY = null;
         SCRIPT_REGISTRY.clear();
         METRIC_OCCURRENCE.clear();
         EXPRESSION_HASHES.clear();
+        EXPRESSION_REGISTRY.clear();
     }
 
     /**
@@ -137,6 +144,7 @@ public final class DSL {
         if (metricName != null && TARGET_DIRECTORY != null) {
             SCRIPT_REGISTRY.put(scriptName, script.getClass().getName());
             EXPRESSION_HASHES.put(scriptName, sha256(expression));
+            EXPRESSION_REGISTRY.put(scriptName, expression);
             log.debug("Compiled MAL script: {} -> {}", scriptName, script.getClass().getName());
         }
 
diff --git a/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalExpression.java b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalExpression.java
new file mode 100644
index 0000000..281318b
--- /dev/null
+++ b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalExpression.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.meter.analyzer.dsl;
+
+import java.util.Map;
+
+/**
+ * Interface for generated MAL (Meter Analysis Language) expression classes.
+ * Each MAL expression from rule YAML files is transpiled to a pure Java class
+ * implementing this interface at build time.
+ *
+ * Replaces the Groovy DelegatingScript approach — no Groovy runtime needed.
+ *
+ * Same-FQCN copy for precompiler build-time classpath.
+ * Canonical version: meter-analyzer-for-graalvm.
+ */
+@FunctionalInterface
+public interface MalExpression {
+
+    /**
+     * Execute the MAL expression against the given sample families.
+     *
+     * @param samples map of metric name → SampleFamily, replaces Groovy's
+     *                propertyMissing() delegate lookup
+     * @return the resulting SampleFamily, or SampleFamily.EMPTY if no data
+     */
+    SampleFamily run(Map<String, SampleFamily> samples);
+}
diff --git a/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalFilter.java b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalFilter.java
new file mode 100644
index 0000000..bfcde47
--- /dev/null
+++ b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalFilter.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.meter.analyzer.dsl;
+
+import java.util.Map;
+
+/**
+ * Interface for generated MAL filter expression classes.
+ * Each filter expression from rule YAML files is transpiled to a pure Java class
+ * implementing this interface at build time.
+ *
+ * Replaces the Groovy Closure&lt;Boolean&gt; approach — no Groovy runtime needed.
+ *
+ * Same-FQCN copy for precompiler build-time classpath.
+ * Canonical version: meter-analyzer-for-graalvm.
+ */
+@FunctionalInterface
+public interface MalFilter {
+
+    /**
+     * Test whether samples with the given tags should be included.
+     *
+     * @param tags the sample labels/tags to evaluate
+     * @return true if the sample should be kept, false to filter it out
+     */
+    boolean test(Map<String, String> tags);
+}
diff --git a/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamilyFunctions.java b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamilyFunctions.java
new file mode 100644
index 0000000..805bd54
--- /dev/null
+++ b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamilyFunctions.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.meter.analyzer.dsl;
+
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import org.apache.skywalking.oap.server.core.analysis.meter.MeterEntity;
+
+/**
+ * Java functional interfaces replacing Groovy Closure parameters in SampleFamily methods.
+ * These are used by the generated MAL expression classes (transpiled from Groovy DSL).
+ *
+ * Same-FQCN copy for precompiler build-time classpath.
+ * Canonical version: meter-analyzer-for-graalvm.
+ */
+public final class SampleFamilyFunctions {
+
+    private SampleFamilyFunctions() {
+    }
+
+    /**
+     * Tag transformation function: receives mutable tag map, modifies it, returns modified map.
+     * Replaces Closure&lt;?&gt; in SampleFamily.tag().
+     */
+    @FunctionalInterface
+    public interface TagFunction extends Function<Map<String, String>, Map<String, String>> {
+    }
+
+    /**
+     * Sample filter predicate: receives tag map, returns true to keep the sample.
+     * Replaces Closure&lt;Boolean&gt; in SampleFamily.filter().
+     */
+    @FunctionalInterface
+    public interface SampleFilter extends Predicate<Map<String, String>> {
+    }
+
+    /**
+     * ForEach function: receives an array element and mutable label map.
+     * Replaces Closure&lt;Void&gt; in SampleFamily.forEach().
+     */
+    @FunctionalInterface
+    public interface ForEachFunction extends BiConsumer<String, Map<String, String>> {
+    }
+
+    /**
+     * Decorate function: receives a MeterEntity to modify.
+     * Replaces Closure&lt;Void&gt; in SampleFamily.decorate().
+     */
+    @FunctionalInterface
+    public interface DecorateFunction extends Consumer<MeterEntity> {
+    }
+
+    /**
+     * Properties extractor: receives label map, returns properties map.
+     * Replaces Closure&lt;Map&gt; in SampleFamily.instance() overload.
+     */
+    @FunctionalInterface
+    public interface PropertiesExtractor extends Function<Map<String, String>, Map<String, String>> {
+    }
+}
diff --git a/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/server/buildtools/precompiler/MalToJavaTranspiler.java b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/server/buildtools/precompiler/MalToJavaTranspiler.java
new file mode 100644
index 0000000..cc7bfb5
--- /dev/null
+++ b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/server/buildtools/precompiler/MalToJavaTranspiler.java
@@ -0,0 +1,1232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.server.buildtools.precompiler;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+import lombok.extern.slf4j.Slf4j;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ModuleNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.DeclarationExpression;
+import org.codehaus.groovy.ast.expr.ElvisOperatorExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.ListExpression;
+import org.codehaus.groovy.ast.expr.MapEntryExpression;
+import org.codehaus.groovy.ast.expr.MapExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.TernaryExpression;
+import org.codehaus.groovy.ast.expr.TupleExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.syntax.Types;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.NotExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.EmptyStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.IfStatement;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.CompilationUnit;
+import org.codehaus.groovy.control.CompilerConfiguration;
+import org.codehaus.groovy.control.Phases;
+
+/**
+ * Transpiles Groovy MAL expressions to Java source code at build time.
+ * Parses expression strings into Groovy AST (via CompilationUnit at CONVERSION phase),
+ * walks AST nodes, and produces equivalent Java classes implementing MalExpression or MalFilter.
+ *
+ * <p>Supported AST patterns:
+ * <ul>
+ *   <li>Variable references: sample family lookups, DownsamplingType constants, KNOWN_TYPES</li>
+ *   <li>Method chains: .sum(), .service(), .tagEqual(), .rate(), .histogram(), etc.</li>
+ *   <li>Binary arithmetic with operand-swap logic per upstream ExpandoMetaClass
+ *       (N-SF: sf.minus(N).negative(), N/SF: sf.newValue(v-&gt;N/v))</li>
+ *   <li>tag() closures: TagFunction lambda (assignment, remove, string concat, if/else)</li>
+ *   <li>Filter closures: MalFilter class (==, !=, in, truthiness, negation, &amp;&amp;, ||)</li>
+ *   <li>forEach() closures: ForEachFunction lambda (var decls, if/else-if, early return)</li>
+ *   <li>instance() with PropertiesExtractor closure: Map.of() from map literals</li>
+ *   <li>Elvis (?:), safe navigation (?.), ternary (? :)</li>
+ *   <li>Batch compilation via javax.tools.JavaCompiler + manifest writing</li>
+ * </ul>
+ *
+ * <p>See MAL-TRANSPILER-DESIGN.md for the full AST-to-Java mapping reference.
+ */
+@Slf4j
+public class MalToJavaTranspiler {
+
+    static final String GENERATED_PACKAGE =
+        "org.apache.skywalking.oap.server.core.source.oal.rt.mal";
+
+    private static final Set<String> DOWNSAMPLING_CONSTANTS = Set.of(
+        "AVG", "SUM", "LATEST", "SUM_PER_MIN", "MAX", "MIN"
+    );
+
+    /** Known enum/class names that appear as property bases (e.g. Layer.GENERAL). */
+    private static final Set<String> KNOWN_TYPES = Set.of(
+        "Layer", "DetectPoint", "K8sRetagType", "ProcessRegistry", "TimeUnit"
+    );
+
+    // ---- Batch state tracking ----
+
+    /** Generated expression sources: className → Java source code. */
+    private final Map<String, String> expressionSources = new LinkedHashMap<>();
+
+    /** Generated filter sources: className → Java source code. */
+    private final Map<String, String> filterSources = new LinkedHashMap<>();
+
+    /** Filter literal to class name mapping for manifest. */
+    private final Map<String, String> filterLiteralToClass = new LinkedHashMap<>();
+
+    /**
+     * Transpile a MAL expression to a Java class source implementing MalExpression.
+     *
+     * @param className  simple class name (e.g. "MalExpr_meter_jvm_heap")
+     * @param expression the Groovy expression string
+     * @return generated Java source code
+     */
+    public String transpileExpression(String className, String expression) {
+        ModuleNode ast = parseToAST(expression);
+        Statement body = extractBody(ast);
+
+        Set<String> sampleNames = new LinkedHashSet<>();
+        collectSampleNames(body, sampleNames);
+
+        String javaBody = visitStatement(body);
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("package ").append(GENERATED_PACKAGE).append(";\n\n");
+        sb.append("import java.util.*;\n");
+        sb.append("import org.apache.skywalking.oap.meter.analyzer.dsl.*;\n");
+        sb.append("import org.apache.skywalking.oap.meter.analyzer.dsl.SampleFamilyFunctions.*;\n");
+        sb.append("import org.apache.skywalking.oap.server.core.analysis.Layer;\n");
+        sb.append("import org.apache.skywalking.oap.server.core.source.DetectPoint;\n");
+        sb.append("import org.apache.skywalking.oap.meter.analyzer.dsl.tagOpt.K8sRetagType;\n");
+        sb.append("import org.apache.skywalking.oap.meter.analyzer.dsl.registry.ProcessRegistry;\n\n");
+
+        sb.append("public class ").append(className).append(" implements MalExpression {\n");
+        sb.append("    @Override\n");
+        sb.append("    public SampleFamily run(Map<String, SampleFamily> samples) {\n");
+
+        if (!sampleNames.isEmpty()) {
+            sb.append("        ExpressionParsingContext.get().ifPresent(ctx -> {\n");
+            for (String name : sampleNames) {
+                sb.append("            ctx.getSamples().add(\"").append(escapeJava(name)).append("\");\n");
+            }
+            sb.append("        });\n");
+        }
+
+        sb.append("        return ").append(javaBody).append(";\n");
+        sb.append("    }\n");
+        sb.append("}\n");
+
+        return sb.toString();
+    }
+
+    /**
+     * Transpile a MAL filter literal to a Java class source implementing MalFilter.
+     * Filter literals are closures like: { tags -> tags.job_name == 'vm-monitoring' }
+     *
+     * @param className     simple class name (e.g. "MalFilter_0")
+     * @param filterLiteral the Groovy closure literal string
+     * @return generated Java source code
+     */
+    public String transpileFilter(String className, String filterLiteral) {
+        ModuleNode ast = parseToAST(filterLiteral);
+        Statement body = extractBody(ast);
+
+        // The filter literal is a closure expression at the top level
+        ClosureExpression closure = extractClosure(body);
+        Parameter[] params = closure.getParameters();
+        String tagsVar = (params != null && params.length > 0) ? params[0].getName() : "tags";
+
+        // Get the body expression — may need to unwrap inner block/closure
+        Expression bodyExpr = extractFilterBodyExpr(closure.getCode(), tagsVar);
+
+        // Generate the boolean condition
+        String condition = visitFilterCondition(bodyExpr, tagsVar);
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("package ").append(GENERATED_PACKAGE).append(";\n\n");
+        sb.append("import java.util.*;\n");
+        sb.append("import org.apache.skywalking.oap.meter.analyzer.dsl.*;\n\n");
+
+        sb.append("public class ").append(className).append(" implements MalFilter {\n");
+        sb.append("    @Override\n");
+        sb.append("    public boolean test(Map<String, String> tags) {\n");
+        sb.append("        return ").append(condition).append(";\n");
+        sb.append("    }\n");
+        sb.append("}\n");
+
+        return sb.toString();
+    }
+
+    /**
+     * Extract the ClosureExpression from a script body.
+     * The filter literal parses as a script whose body is a single ClosureExpression.
+     */
+    private ClosureExpression extractClosure(Statement body) {
+        List<Statement> stmts = getStatements(body);
+        if (stmts.size() == 1 && stmts.get(0) instanceof ExpressionStatement) {
+            Expression expr = ((ExpressionStatement) stmts.get(0)).getExpression();
+            if (expr instanceof ClosureExpression) {
+                return (ClosureExpression) expr;
+            }
+        }
+        throw new IllegalStateException(
+            "Filter literal must be a single closure expression, got: "
+                + (stmts.isEmpty() ? "empty" : stmts.get(0).getClass().getSimpleName()));
+    }
+
+    /**
+     * Extract the boolean expression from a filter closure body.
+     * Handles the "wrapped block" pattern: { tags -> {compound} }
+     * where the inner {} is another ClosureExpression that must be unwrapped.
+     */
+    private Expression extractFilterBodyExpr(Statement code, String tagsVar) {
+        List<Statement> stmts = getStatements(code);
+        if (stmts.isEmpty()) {
+            throw new IllegalStateException("Empty filter closure body");
+        }
+
+        // Get the last statement's expression
+        Statement last = stmts.get(stmts.size() - 1);
+        Expression expr;
+        if (last instanceof ExpressionStatement) {
+            expr = ((ExpressionStatement) last).getExpression();
+        } else if (last instanceof ReturnStatement) {
+            expr = ((ReturnStatement) last).getExpression();
+        } else if (last instanceof BlockStatement) {
+            // Nested block — recurse to unwrap
+            return extractFilterBodyExpr(last, tagsVar);
+        } else {
+            throw new UnsupportedOperationException(
+                "Unsupported filter body statement: " + last.getClass().getSimpleName());
+        }
+
+        // Unwrap inner closure: { tags -> {compound} } → the inner closure body
+        if (expr instanceof ClosureExpression) {
+            ClosureExpression inner = (ClosureExpression) expr;
+            return extractFilterBodyExpr(inner.getCode(), tagsVar);
+        }
+
+        return expr;
+    }
+
+    // ---- AST Parsing ----
+
+    ModuleNode parseToAST(String expression) {
+        CompilerConfiguration cc = new CompilerConfiguration();
+        CompilationUnit cu = new CompilationUnit(cc);
+        cu.addSource("Script", expression);
+        cu.compile(Phases.CONVERSION);
+        List<ModuleNode> modules = cu.getAST().getModules();
+        if (modules.isEmpty()) {
+            throw new IllegalStateException("No AST modules produced for: " + expression);
+        }
+        return modules.get(0);
+    }
+
+    Statement extractBody(ModuleNode module) {
+        BlockStatement block = module.getStatementBlock();
+        if (block != null && !block.getStatements().isEmpty()) {
+            return block;
+        }
+        List<ClassNode> classes = module.getClasses();
+        if (!classes.isEmpty()) {
+            return module.getStatementBlock();
+        }
+        throw new IllegalStateException("Empty AST body");
+    }
+
+    // ---- Sample Name Collection ----
+
+    private void collectSampleNames(Statement stmt, Set<String> names) {
+        if (stmt instanceof BlockStatement) {
+            for (Statement s : ((BlockStatement) stmt).getStatements()) {
+                collectSampleNames(s, names);
+            }
+        } else if (stmt instanceof ExpressionStatement) {
+            collectSampleNamesFromExpr(((ExpressionStatement) stmt).getExpression(), names);
+        } else if (stmt instanceof ReturnStatement) {
+            collectSampleNamesFromExpr(((ReturnStatement) stmt).getExpression(), names);
+        }
+    }
+
+    void collectSampleNamesFromExpr(Expression expr, Set<String> names) {
+        if (expr instanceof VariableExpression) {
+            String name = ((VariableExpression) expr).getName();
+            if (!DOWNSAMPLING_CONSTANTS.contains(name)
+                && !KNOWN_TYPES.contains(name)
+                && !name.equals("this") && !name.equals("time")) {
+                names.add(name);
+            }
+        } else if (expr instanceof BinaryExpression) {
+            BinaryExpression bin = (BinaryExpression) expr;
+            collectSampleNamesFromExpr(bin.getLeftExpression(), names);
+            collectSampleNamesFromExpr(bin.getRightExpression(), names);
+        } else if (expr instanceof PropertyExpression) {
+            PropertyExpression pe = (PropertyExpression) expr;
+            collectSampleNamesFromExpr(pe.getObjectExpression(), names);
+        } else if (expr instanceof MethodCallExpression) {
+            MethodCallExpression mce = (MethodCallExpression) expr;
+            collectSampleNamesFromExpr(mce.getObjectExpression(), names);
+            collectSampleNamesFromExpr(mce.getArguments(), names);
+        } else if (expr instanceof ArgumentListExpression) {
+            for (Expression e : ((ArgumentListExpression) expr).getExpressions()) {
+                collectSampleNamesFromExpr(e, names);
+            }
+        } else if (expr instanceof TupleExpression) {
+            for (Expression e : ((TupleExpression) expr).getExpressions()) {
+                collectSampleNamesFromExpr(e, names);
+            }
+        }
+    }
+
+    // ---- Statement Visiting ----
+
+    String visitStatement(Statement stmt) {
+        if (stmt instanceof BlockStatement) {
+            List<Statement> stmts = ((BlockStatement) stmt).getStatements();
+            if (stmts.size() == 1) {
+                return visitStatement(stmts.get(0));
+            }
+            // Multi-statement: last one is the return value
+            return visitStatement(stmts.get(stmts.size() - 1));
+        } else if (stmt instanceof ExpressionStatement) {
+            return visitExpression(((ExpressionStatement) stmt).getExpression());
+        } else if (stmt instanceof ReturnStatement) {
+            return visitExpression(((ReturnStatement) stmt).getExpression());
+        }
+        throw new UnsupportedOperationException(
+            "Unsupported statement: " + stmt.getClass().getSimpleName());
+    }
+
+    // ---- Expression Visiting ----
+
+    String visitExpression(Expression expr) {
+        if (expr instanceof VariableExpression) {
+            return visitVariable((VariableExpression) expr);
+        } else if (expr instanceof ConstantExpression) {
+            return visitConstant((ConstantExpression) expr);
+        } else if (expr instanceof MethodCallExpression) {
+            return visitMethodCall((MethodCallExpression) expr);
+        } else if (expr instanceof PropertyExpression) {
+            return visitProperty((PropertyExpression) expr);
+        } else if (expr instanceof ListExpression) {
+            return visitList((ListExpression) expr);
+        } else if (expr instanceof BinaryExpression) {
+            return visitBinary((BinaryExpression) expr);
+        } else if (expr instanceof ClosureExpression) {
+            // Bare closure not in a recognized method context — shouldn't happen in MAL
+            throw new UnsupportedOperationException(
+                "Bare ClosureExpression outside method call context: " + expr.getText());
+        }
+        throw new UnsupportedOperationException(
+            "Unsupported expression (not yet implemented): "
+                + expr.getClass().getSimpleName() + " = " + expr.getText());
+    }
+
+    private String visitVariable(VariableExpression expr) {
+        String name = expr.getName();
+        if (DOWNSAMPLING_CONSTANTS.contains(name)) {
+            return "DownsamplingType." + name;
+        }
+        if (KNOWN_TYPES.contains(name)) {
+            return name;
+        }
+        if (name.equals("this")) {
+            return "this";
+        }
+        // Sample family lookup
+        return "samples.getOrDefault(\"" + escapeJava(name) + "\", SampleFamily.EMPTY)";
+    }
+
+    private String visitConstant(ConstantExpression expr) {
+        Object value = expr.getValue();
+        if (value == null) {
+            return "null";
+        }
+        if (value instanceof String) {
+            return "\"" + escapeJava((String) value) + "\"";
+        }
+        if (value instanceof Integer) {
+            return value.toString();
+        }
+        if (value instanceof Long) {
+            return value + "L";
+        }
+        if (value instanceof Double) {
+            return value.toString();
+        }
+        if (value instanceof Float) {
+            return value + "f";
+        }
+        if (value instanceof Boolean) {
+            return value.toString();
+        }
+        return value.toString();
+    }
+
+    // ---- MethodCall, Property, List ----
+
+    private String visitMethodCall(MethodCallExpression expr) {
+        String methodName = expr.getMethodAsString();
+        Expression objExpr = expr.getObjectExpression();
+        ArgumentListExpression args = toArgList(expr.getArguments());
+
+        // tag(closure) → TagFunction lambda
+        if ("tag".equals(methodName) && args.getExpressions().size() == 1
+            && args.getExpression(0) instanceof ClosureExpression) {
+            String obj = visitExpression(objExpr);
+            String lambda = visitTagClosure((ClosureExpression) args.getExpression(0));
+            return obj + ".tag((TagFunction) " + lambda + ")";
+        }
+
+        // forEach(list, closure) → ForEachFunction lambda
+        if ("forEach".equals(methodName) && args.getExpressions().size() == 2
+            && args.getExpression(1) instanceof ClosureExpression) {
+            String obj = visitExpression(objExpr);
+            String list = visitExpression(args.getExpression(0));
+            String lambda = visitForEachClosure((ClosureExpression) args.getExpression(1));
+            return obj + ".forEach(" + list + ", (ForEachFunction) " + lambda + ")";
+        }
+
+        // instance(..., closure) → last arg is PropertiesExtractor lambda (Step 9)
+        if ("instance".equals(methodName) && !args.getExpressions().isEmpty()) {
+            Expression lastArg = args.getExpression(args.getExpressions().size() - 1);
+            if (lastArg instanceof ClosureExpression) {
+                String obj = visitExpression(objExpr);
+                List<String> argStrs = new ArrayList<>();
+                for (int i = 0; i < args.getExpressions().size() - 1; i++) {
+                    argStrs.add(visitExpression(args.getExpression(i)));
+                }
+                String lambda = visitPropertiesExtractorClosure((ClosureExpression) lastArg);
+                argStrs.add("(PropertiesExtractor) " + lambda);
+                return obj + ".instance(" + String.join(", ", argStrs) + ")";
+            }
+        }
+
+        String obj = visitExpression(objExpr);
+
+        // Static method calls: ClassExpression.method(...)
+        // e.g. ProcessRegistry.generateVirtualLocalProcess(...)
+        if (objExpr instanceof ClassExpression) {
+            String typeName = objExpr.getType().getNameWithoutPackage();
+            List<String> argStrs = visitArgList(args);
+            return typeName + "." + methodName + "(" + String.join(", ", argStrs) + ")";
+        }
+
+        // Regular instance method call: obj.method(args)
+        List<String> argStrs = visitArgList(args);
+        return obj + "." + methodName + "(" + String.join(", ", argStrs) + ")";
+    }
+
+    private String visitProperty(PropertyExpression expr) {
+        Expression obj = expr.getObjectExpression();
+        String prop = expr.getPropertyAsString();
+
+        // Enum access: Layer.GENERAL, DetectPoint.SERVER, K8sRetagType.Pod2Service
+        // At CONVERSION phase, Groovy doesn't resolve types, so these appear as
+        // PropertyExpression(VariableExpression("Layer"), "GENERAL") not ClassExpression.
+        if (obj instanceof ClassExpression) {
+            return obj.getType().getNameWithoutPackage() + "." + prop;
+        }
+        if (obj instanceof VariableExpression) {
+            String varName = ((VariableExpression) obj).getName();
+            if (KNOWN_TYPES.contains(varName)) {
+                return varName + "." + prop;
+            }
+        }
+
+        // Fallback: obj.prop
+        return visitExpression(obj) + "." + prop;
+    }
+
+    private String visitList(ListExpression expr) {
+        List<String> elements = new ArrayList<>();
+        for (Expression e : expr.getExpressions()) {
+            elements.add(visitExpression(e));
+        }
+        return "List.of(" + String.join(", ", elements) + ")";
+    }
+
+    // ---- Binary Arithmetic ----
+
+    private String visitBinary(BinaryExpression expr) {
+        int opType = expr.getOperation().getType();
+
+        if (isArithmetic(opType)) {
+            return visitArithmetic(expr.getLeftExpression(), expr.getRightExpression(), opType);
+        }
+
+        // Non-arithmetic binary ops handled in closure-specific visitors
+        throw new UnsupportedOperationException(
+            "Unsupported binary operator (not yet implemented): "
+                + expr.getOperation().getText() + " in " + expr.getText());
+    }
+
+    /**
+     * Arithmetic with operand-swap logic per upstream ExpandoMetaClass:
+     * <pre>
+     *   SF + SF  → left.plus(right)
+     *   SF - SF  → left.minus(right)
+     *   SF * SF  → left.multiply(right)
+     *   SF / SF  → left.div(right)
+     *   SF op N  → sf.op(N)
+     *   N + SF   → sf.plus(N)          (swap)
+     *   N - SF   → sf.minus(N).negative()
+     *   N * SF   → sf.multiply(N)      (swap)
+     *   N / SF   → sf.newValue(v → N / v)
+     *   N op N   → plain arithmetic
+     * </pre>
+     */
+    private String visitArithmetic(Expression left, Expression right, int opType) {
+        boolean leftNum = isNumberLiteral(left);
+        boolean rightNum = isNumberLiteral(right);
+        String leftStr = visitExpression(left);
+        String rightStr = visitExpression(right);
+
+        if (leftNum && rightNum) {
+            // N op N — plain arithmetic
+            return "(" + leftStr + " " + opSymbol(opType) + " " + rightStr + ")";
+        }
+
+        if (!leftNum && rightNum) {
+            // SF op N
+            return leftStr + "." + opMethod(opType) + "(" + rightStr + ")";
+        }
+
+        if (leftNum && !rightNum) {
+            // N op SF — operand swap per ExpandoMetaClass
+            switch (opType) {
+                case Types.PLUS:
+                    return rightStr + ".plus(" + leftStr + ")";
+                case Types.MINUS:
+                    return rightStr + ".minus(" + leftStr + ").negative()";
+                case Types.MULTIPLY:
+                    return rightStr + ".multiply(" + leftStr + ")";
+                case Types.DIVIDE:
+                    return rightStr + ".newValue(v -> " + leftStr + " / v)";
+                default:
+                    break;
+            }
+        }
+
+        // SF op SF
+        return leftStr + "." + opMethod(opType) + "(" + rightStr + ")";
+    }
+
+    private boolean isNumberLiteral(Expression expr) {
+        if (expr instanceof ConstantExpression) {
+            return ((ConstantExpression) expr).getValue() instanceof Number;
+        }
+        return false;
+    }
+
+    private boolean isArithmetic(int opType) {
+        return opType == Types.PLUS || opType == Types.MINUS
+            || opType == Types.MULTIPLY || opType == Types.DIVIDE;
+    }
+
+    private String opMethod(int opType) {
+        switch (opType) {
+            case Types.PLUS: return "plus";
+            case Types.MINUS: return "minus";
+            case Types.MULTIPLY: return "multiply";
+            case Types.DIVIDE: return "div";
+            default: return "???";
+        }
+    }
+
+    private String opSymbol(int opType) {
+        switch (opType) {
+            case Types.PLUS: return "+";
+            case Types.MINUS: return "-";
+            case Types.MULTIPLY: return "*";
+            case Types.DIVIDE: return "/";
+            default: return "?";
+        }
+    }
+
+    // ---- tag() Closure ----
+
+    /**
+     * Translate a tag closure to a TagFunction lambda.
+     * Input:  {tags -> tags.key = 'val'; tags.remove('x'); ...}
+     * Output: (tags -> { tags.put("key", "val"); tags.remove("x"); ... return tags; })
+     */
+    private String visitTagClosure(ClosureExpression closure) {
+        Parameter[] params = closure.getParameters();
+        String tagsVar = (params != null && params.length > 0) ? params[0].getName() : "tags";
+        List<Statement> stmts = getStatements(closure.getCode());
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("(").append(tagsVar).append(" -> {\n");
+        for (Statement s : stmts) {
+            sb.append("            ").append(visitTagStatement(s, tagsVar)).append("\n");
+        }
+        sb.append("            return ").append(tagsVar).append(";\n");
+        sb.append("        })");
+        return sb.toString();
+    }
+
+    private String visitTagStatement(Statement stmt, String tagsVar) {
+        if (stmt instanceof ExpressionStatement) {
+            return visitTagExpr(((ExpressionStatement) stmt).getExpression(), tagsVar) + ";";
+        }
+        if (stmt instanceof ReturnStatement) {
+            return "return " + tagsVar + ";";
+        }
+        if (stmt instanceof IfStatement) {
+            return visitTagIf((IfStatement) stmt, tagsVar);
+        }
+        throw new UnsupportedOperationException(
+            "Unsupported tag closure statement: " + stmt.getClass().getSimpleName());
+    }
+
+    // ---- If/Else + Compound Conditions in tag() ----
+
+    /**
+     * Translate an if/else inside a tag closure.
+     * <pre>
+     *   if (tags['gc'] == 'Copy' || tags['gc'] == 'ParNew') { tags.gc = 'young' }
+     *   else { tags.gc = 'old' }
+     * </pre>
+     * → Java if/else with .equals() for string comparisons.
+     */
+    private String visitTagIf(IfStatement ifStmt, String tagsVar) {
+        String condition = visitTagCondition(ifStmt.getBooleanExpression().getExpression(), tagsVar);
+        List<Statement> ifBody = getStatements(ifStmt.getIfBlock());
+        Statement elseBlock = ifStmt.getElseBlock();
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("if (").append(condition).append(") {\n");
+        for (Statement s : ifBody) {
+            sb.append("                ").append(visitTagStatement(s, tagsVar)).append("\n");
+        }
+        sb.append("            }");
+
+        if (elseBlock != null && !(elseBlock instanceof EmptyStatement)) {
+            sb.append(" else {\n");
+            List<Statement> elseBody = getStatements(elseBlock);
+            for (Statement s : elseBody) {
+                sb.append("                ").append(visitTagStatement(s, tagsVar)).append("\n");
+            }
+            sb.append("            }");
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Translate a condition expression inside a tag closure.
+     * Handles ==, !=, ||, && comparisons against tag values.
+     * Groovy == translates to .equals() in Java for null-safety.
+     */
+    private String visitTagCondition(Expression expr, String tagsVar) {
+        if (expr instanceof BinaryExpression) {
+            BinaryExpression bin = (BinaryExpression) expr;
+            int opType = bin.getOperation().getType();
+
+            if (opType == Types.COMPARE_EQUAL) {
+                return visitTagEquals(bin.getLeftExpression(), bin.getRightExpression(), tagsVar, false);
+            }
+            if (opType == Types.COMPARE_NOT_EQUAL) {
+                return visitTagEquals(bin.getLeftExpression(), bin.getRightExpression(), tagsVar, true);
+            }
+            if (opType == Types.LOGICAL_OR) {
+                return visitTagCondition(bin.getLeftExpression(), tagsVar)
+                    + " || " + visitTagCondition(bin.getRightExpression(), tagsVar);
+            }
+            if (opType == Types.LOGICAL_AND) {
+                return visitTagCondition(bin.getLeftExpression(), tagsVar)
+                    + " && " + visitTagCondition(bin.getRightExpression(), tagsVar);
+            }
+        }
+        if (expr instanceof BooleanExpression) {
+            return visitTagCondition(((BooleanExpression) expr).getExpression(), tagsVar);
+        }
+        // Fallback: treat as a tag value expression (truthiness)
+        return visitTagValue(expr, tagsVar);
+    }
+
+    /**
+     * Translate == or != comparison in tag context.
+     * Puts constant string on the left for null-safety:
+     *   tags['gc'] == 'Copy' → "Copy".equals(tags.get("gc"))
+     *   tags['gc'] != 'idle' → !"idle".equals(tags.get("gc"))
+     */
+    private String visitTagEquals(Expression left, Expression right, String tagsVar, boolean negate) {
+        // Null comparison: expr == null or expr != null
+        if (isNullConstant(right)) {
+            String leftStr = visitTagValue(left, tagsVar);
+            return negate ? leftStr + " != null" : leftStr + " == null";
+        }
+        if (isNullConstant(left)) {
+            String rightStr = visitTagValue(right, tagsVar);
+            return negate ? rightStr + " != null" : rightStr + " == null";
+        }
+
+        String leftStr = visitTagValue(left, tagsVar);
+        String rightStr = visitTagValue(right, tagsVar);
+
+        // If right side is a string constant, put it on left for null-safety
+        if (right instanceof ConstantExpression && ((ConstantExpression) right).getValue() instanceof String) {
+            String result = rightStr + ".equals(" + leftStr + ")";
+            return negate ? "!" + result : result;
+        }
+        // If left side is a string constant
+        if (left instanceof ConstantExpression && ((ConstantExpression) left).getValue() instanceof String) {
+            String result = leftStr + ".equals(" + rightStr + ")";
+            return negate ? "!" + result : result;
+        }
+        // Neither side is constant string — use Objects.equals
+        String result = "Objects.equals(" + leftStr + ", " + rightStr + ")";
+        return negate ? "!" + result : result;
+    }
+
+    // ---- Filter Conditions ----
+
+    /**
+     * Visit a condition expression in a filter context.
+     * Extends tag condition handling with:
+     * - {@code in} operator: {@code tags.key in ['a','b']} → {@code List.of("a","b").contains(tags.get("key"))}
+     * - Truthiness: bare {@code tags.key} → {@code tags.get("key") != null && !tags.get("key").isEmpty()}
+     * - Negation: {@code !tags.key} → {@code tags.get("key") == null || tags.get("key").isEmpty()}
+     */
+    private String visitFilterCondition(Expression expr, String tagsVar) {
+        // NotExpression: !tags.key → truthiness negation
+        if (expr instanceof NotExpression) {
+            Expression inner = ((NotExpression) expr).getExpression();
+            String val = visitTagValue(inner, tagsVar);
+            return "(" + val + " == null || " + val + ".isEmpty())";
+        }
+
+        if (expr instanceof BinaryExpression) {
+            BinaryExpression bin = (BinaryExpression) expr;
+            int opType = bin.getOperation().getType();
+
+            // Reuse == and != from tag conditions
+            if (opType == Types.COMPARE_EQUAL) {
+                return visitTagEquals(bin.getLeftExpression(), bin.getRightExpression(), tagsVar, false);
+            }
+            if (opType == Types.COMPARE_NOT_EQUAL) {
+                return visitTagEquals(bin.getLeftExpression(), bin.getRightExpression(), tagsVar, true);
+            }
+            // Logical operators — recurse with visitFilterCondition
+            if (opType == Types.LOGICAL_OR) {
+                return visitFilterCondition(bin.getLeftExpression(), tagsVar)
+                    + " || " + visitFilterCondition(bin.getRightExpression(), tagsVar);
+            }
+            if (opType == Types.LOGICAL_AND) {
+                return visitFilterCondition(bin.getLeftExpression(), tagsVar)
+                    + " && " + visitFilterCondition(bin.getRightExpression(), tagsVar);
+            }
+            // IN operator: tags.key in ['a', 'b'] → List.of("a","b").contains(tags.get("key"))
+            if (opType == Types.KEYWORD_IN) {
+                String val = visitTagValue(bin.getLeftExpression(), tagsVar);
+                String list = visitTagValue(bin.getRightExpression(), tagsVar);
+                return list + ".contains(" + val + ")";
+            }
+        }
+
+        if (expr instanceof BooleanExpression) {
+            return visitFilterCondition(((BooleanExpression) expr).getExpression(), tagsVar);
+        }
+
+        // Fallback: treat as truthiness check on a tag value
+        // tags.key → tags.get("key") != null && !tags.get("key").isEmpty()
+        String val = visitTagValue(expr, tagsVar);
+        return "(" + val + " != null && !" + val + ".isEmpty())";
+    }
+
+    /**
+     * Visit an expression inside a tag closure body.
+     * Handles assignment (put), remove, and other tag operations.
+     */
+    private String visitTagExpr(Expression expr, String tagsVar) {
+        // Assignment: tags.key = val  or  tags[key] = val
+        if (expr instanceof BinaryExpression) {
+            BinaryExpression bin = (BinaryExpression) expr;
+            if (bin.getOperation().getType() == Types.ASSIGN) {
+                return visitTagAssignment(bin.getLeftExpression(), bin.getRightExpression(), tagsVar);
+            }
+        }
+        // Method call: tags.remove('key')
+        if (expr instanceof MethodCallExpression) {
+            MethodCallExpression mce = (MethodCallExpression) expr;
+            if ("remove".equals(mce.getMethodAsString()) && isTagsVar(mce.getObjectExpression(), tagsVar)) {
+                ArgumentListExpression args = toArgList(mce.getArguments());
+                return tagsVar + ".remove(" + visitTagValue(args.getExpression(0), tagsVar) + ")";
+            }
+        }
+        return visitTagValue(expr, tagsVar);
+    }
+
+    /**
+     * Visit a tag assignment: LHS = RHS.
+     * tags.key = val  → tags.put("key", val)
+     * tags[expr] = val → tags.put(expr, val)
+     */
+    private String visitTagAssignment(Expression left, Expression right, String tagsVar) {
+        String val = visitTagValue(right, tagsVar);
+
+        // tags.key = val → PropertyExpression
+        if (left instanceof PropertyExpression) {
+            PropertyExpression prop = (PropertyExpression) left;
+            if (isTagsVar(prop.getObjectExpression(), tagsVar)) {
+                return tagsVar + ".put(\"" + escapeJava(prop.getPropertyAsString()) + "\", " + val + ")";
+            }
+        }
+        // tags[expr] = val → BinaryExpression with LEFT_SQUARE_BRACKET
+        if (left instanceof BinaryExpression) {
+            BinaryExpression sub = (BinaryExpression) left;
+            if (sub.getOperation().getType() == Types.LEFT_SQUARE_BRACKET
+                && isTagsVar(sub.getLeftExpression(), tagsVar)) {
+                String key = visitTagValue(sub.getRightExpression(), tagsVar);
+                return tagsVar + ".put(" + key + ", " + val + ")";
+            }
+        }
+        throw new UnsupportedOperationException(
+            "Unsupported tag assignment target: " + left.getClass().getSimpleName() + " = " + left.getText());
+    }
+
+    /**
+     * Visit a value expression inside a tag closure.
+     * Translates tags.key → tags.get("key"), tags['key'] → tags.get("key"),
+     * string concat, method calls on tag values, etc.
+     */
+    String visitTagValue(Expression expr, String tagsVar) {
+        // tags.key → tags.get("key")
+        if (expr instanceof PropertyExpression) {
+            PropertyExpression prop = (PropertyExpression) expr;
+            if (isTagsVar(prop.getObjectExpression(), tagsVar)) {
+                return tagsVar + ".get(\"" + escapeJava(prop.getPropertyAsString()) + "\")";
+            }
+            // Not tags — could be enum like Layer.GENERAL
+            return visitProperty(prop);
+        }
+        // tags[expr] → tags.get(expr)
+        if (expr instanceof BinaryExpression) {
+            BinaryExpression bin = (BinaryExpression) expr;
+            if (bin.getOperation().getType() == Types.LEFT_SQUARE_BRACKET
+                && isTagsVar(bin.getLeftExpression(), tagsVar)) {
+                return tagsVar + ".get(" + visitTagValue(bin.getRightExpression(), tagsVar) + ")";
+            }
+            // String concatenation or other binary in tag context
+            if (bin.getOperation().getType() == Types.PLUS) {
+                return visitTagValue(bin.getLeftExpression(), tagsVar)
+                    + " + " + visitTagValue(bin.getRightExpression(), tagsVar);
+            }
+        }
+        // Elvis operator — must check BEFORE TernaryExpression since it extends it
+        // expr ?: 'default' → (expr != null ? expr : "default")
+        if (expr instanceof ElvisOperatorExpression) {
+            ElvisOperatorExpression elvis = (ElvisOperatorExpression) expr;
+            String val = visitTagValue(elvis.getTrueExpression(), tagsVar);
+            String defaultVal = visitTagValue(elvis.getFalseExpression(), tagsVar);
+            return "(" + val + " != null ? " + val + " : " + defaultVal + ")";
+        }
+        // Ternary operator — tags.ApiId ? exprA : exprB
+        if (expr instanceof TernaryExpression) {
+            TernaryExpression tern = (TernaryExpression) expr;
+            String cond = visitFilterCondition(tern.getBooleanExpression().getExpression(), tagsVar);
+            String trueVal = visitTagValue(tern.getTrueExpression(), tagsVar);
+            String falseVal = visitTagValue(tern.getFalseExpression(), tagsVar);
+            return "(" + cond + " ? " + trueVal + " : " + falseVal + ")";
+        }
+        // Method call / safe navigation — tags['key']?.trim() → null check
+        if (expr instanceof MethodCallExpression) {
+            MethodCallExpression mce = (MethodCallExpression) expr;
+            String obj = visitTagValue(mce.getObjectExpression(), tagsVar);
+            ArgumentListExpression args = toArgList(mce.getArguments());
+            List<String> argStrs = new ArrayList<>();
+            for (Expression a : args.getExpressions()) {
+                argStrs.add(visitTagValue(a, tagsVar));
+            }
+            String call = obj + "." + mce.getMethodAsString() + "(" + String.join(", ", argStrs) + ")";
+            if (mce.isSafe()) {
+                return "(" + obj + " != null ? " + call + " : null)";
+            }
+            return call;
+        }
+        // Variable reference — could be tagsVar itself or a closure parameter
+        if (expr instanceof VariableExpression) {
+            String name = ((VariableExpression) expr).getName();
+            if (name.equals(tagsVar)) {
+                return tagsVar;
+            }
+            // Other variables: could be a local var or closure param
+            return name;
+        }
+        // Constant
+        if (expr instanceof ConstantExpression) {
+            return visitConstant((ConstantExpression) expr);
+        }
+        // List
+        if (expr instanceof ListExpression) {
+            return visitList((ListExpression) expr);
+        }
+        // Map literal — ['pod': tags.pod, 'namespace': tags.namespace]
+        if (expr instanceof MapExpression) {
+            MapExpression map = (MapExpression) expr;
+            List<String> entries = new ArrayList<>();
+            for (MapEntryExpression entry : map.getMapEntryExpressions()) {
+                entries.add(visitTagValue(entry.getKeyExpression(), tagsVar));
+                entries.add(visitTagValue(entry.getValueExpression(), tagsVar));
+            }
+            return "Map.of(" + String.join(", ", entries) + ")";
+        }
+        // Fallback
+        return visitExpression(expr);
+    }
+
+    private boolean isTagsVar(Expression expr, String tagsVar) {
+        return expr instanceof VariableExpression
+            && ((VariableExpression) expr).getName().equals(tagsVar);
+    }
+
+    private List<Statement> getStatements(Statement stmt) {
+        if (stmt instanceof BlockStatement) {
+            return ((BlockStatement) stmt).getStatements();
+        }
+        return List.of(stmt);
+    }
+
+    // ---- forEach() Closure ----
+
+    /**
+     * Translate a forEach closure to a ForEachFunction lambda.
+     * Input:  forEach(['client','server'], {prefix, tags -> ...})
+     * Output: .forEach(List.of("client","server"), (ForEachFunction) (prefix, tags) -> { ... })
+     */
+    private String visitForEachClosure(ClosureExpression closure) {
+        Parameter[] params = closure.getParameters();
+        String prefixVar = (params != null && params.length > 0) ? params[0].getName() : "prefix";
+        String tagsVar = (params != null && params.length > 1) ? params[1].getName() : "tags";
+
+        List<Statement> stmts = getStatements(closure.getCode());
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("(").append(prefixVar).append(", ").append(tagsVar).append(") -> {\n");
+        for (Statement s : stmts) {
+            sb.append("            ").append(visitForEachStatement(s, tagsVar)).append("\n");
+        }
+        sb.append("        }");
+        return sb.toString();
+    }
+
+    private String visitForEachStatement(Statement stmt, String tagsVar) {
+        if (stmt instanceof ExpressionStatement) {
+            return visitForEachExpr(((ExpressionStatement) stmt).getExpression(), tagsVar) + ";";
+        }
+        if (stmt instanceof ReturnStatement) {
+            // forEach is void (BiConsumer) — return always has no value
+            return "return;";
+        }
+        if (stmt instanceof IfStatement) {
+            return visitForEachIf((IfStatement) stmt, tagsVar);
+        }
+        throw new UnsupportedOperationException(
+            "Unsupported forEach closure statement: " + stmt.getClass().getSimpleName());
+    }
+
+    /**
+     * Visit an expression inside a forEach closure body.
+     * Handles variable declarations, tag writes, local variable assignments.
+     */
+    private String visitForEachExpr(Expression expr, String tagsVar) {
+        // Variable declaration: String result = ""
+        if (expr instanceof DeclarationExpression) {
+            DeclarationExpression decl = (DeclarationExpression) expr;
+            String typeName = decl.getVariableExpression().getType().getNameWithoutPackage();
+            String varName = decl.getVariableExpression().getName();
+            String init = visitTagValue(decl.getRightExpression(), tagsVar);
+            return typeName + " " + varName + " = " + init;
+        }
+        // Assignment
+        if (expr instanceof BinaryExpression) {
+            BinaryExpression bin = (BinaryExpression) expr;
+            if (bin.getOperation().getType() == Types.ASSIGN) {
+                Expression left = bin.getLeftExpression();
+                // Tag write: tags.key = val or tags[expr] = val
+                if (isTagWrite(left, tagsVar)) {
+                    return visitTagAssignment(left, bin.getRightExpression(), tagsVar);
+                }
+                // Local variable assignment: result = '129'
+                if (left instanceof VariableExpression) {
+                    return ((VariableExpression) left).getName()
+                        + " = " + visitTagValue(bin.getRightExpression(), tagsVar);
+                }
+            }
+        }
+        // Method call or other expression
+        return visitTagExpr(expr, tagsVar);
+    }
+
+    /**
+     * Translate if/else-if/else chains inside a forEach closure.
+     * Handles chained else-if by detecting IfStatement as else block.
+     */
+    private String visitForEachIf(IfStatement ifStmt, String tagsVar) {
+        String condition = visitTagCondition(ifStmt.getBooleanExpression().getExpression(), tagsVar);
+        List<Statement> ifBody = getStatements(ifStmt.getIfBlock());
+        Statement elseBlock = ifStmt.getElseBlock();
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("if (").append(condition).append(") {\n");
+        for (Statement s : ifBody) {
+            sb.append("                ").append(visitForEachStatement(s, tagsVar)).append("\n");
+        }
+        sb.append("            }");
+
+        if (elseBlock instanceof IfStatement) {
+            // else if chain
+            sb.append(" else ").append(visitForEachIf((IfStatement) elseBlock, tagsVar));
+        } else if (elseBlock != null && !(elseBlock instanceof EmptyStatement)) {
+            sb.append(" else {\n");
+            for (Statement s : getStatements(elseBlock)) {
+                sb.append("                ").append(visitForEachStatement(s, tagsVar)).append("\n");
+            }
+            sb.append("            }");
+        }
+
+        return sb.toString();
+    }
+
+    private boolean isTagWrite(Expression left, String tagsVar) {
+        if (left instanceof PropertyExpression) {
+            return isTagsVar(((PropertyExpression) left).getObjectExpression(), tagsVar);
+        }
+        if (left instanceof BinaryExpression) {
+            BinaryExpression sub = (BinaryExpression) left;
+            return sub.getOperation().getType() == Types.LEFT_SQUARE_BRACKET
+                && isTagsVar(sub.getLeftExpression(), tagsVar);
+        }
+        return false;
+    }
+
+    private boolean isNullConstant(Expression expr) {
+        return expr instanceof ConstantExpression && ((ConstantExpression) expr).getValue() == null;
+    }
+
+    // ---- PropertiesExtractor Closure ----
+
+    /**
+     * Translate a PropertiesExtractor closure.
+     * Input:  { tags -> ['pod': tags.pod, 'namespace': tags.namespace] }
+     * Output: (tags -> Map.of("pod", tags.get("pod"), "namespace", tags.get("namespace")))
+     */
+    private String visitPropertiesExtractorClosure(ClosureExpression closure) {
+        Parameter[] params = closure.getParameters();
+        String tagsVar = (params != null && params.length > 0) ? params[0].getName() : "tags";
+        List<Statement> stmts = getStatements(closure.getCode());
+
+        Statement last = stmts.get(stmts.size() - 1);
+        Expression bodyExpr;
+        if (last instanceof ExpressionStatement) {
+            bodyExpr = ((ExpressionStatement) last).getExpression();
+        } else if (last instanceof ReturnStatement) {
+            bodyExpr = ((ReturnStatement) last).getExpression();
+        } else {
+            throw new UnsupportedOperationException(
+                "Unsupported PropertiesExtractor closure body: " + last.getClass().getSimpleName());
+        }
+        return "(" + tagsVar + " -> " + visitTagValue(bodyExpr, tagsVar) + ")";
+    }
+
+    // ---- Batch Registration, Compilation, and Manifest Writing ----
+
+    /**
+     * Register a transpiled expression for batch compilation.
+     */
+    public void registerExpression(String className, String source) {
+        expressionSources.put(className, source);
+    }
+
+    /**
+     * Register a transpiled filter for batch compilation.
+     */
+    public void registerFilter(String className, String filterLiteral, String source) {
+        filterSources.put(className, source);
+        filterLiteralToClass.put(filterLiteral, GENERATED_PACKAGE + "." + className);
+    }
+
+    /**
+     * Compile all registered sources using javax.tools.JavaCompiler.
+     *
+     * @param sourceDir  directory to write .java source files (package dirs created automatically)
+     * @param outputDir  directory for compiled .class files
+     * @param classpath  classpath for javac (semicolon/colon-separated JAR paths)
+     * @throws IOException if file I/O fails
+     */
+    public void compileAll(File sourceDir, File outputDir, String classpath) throws IOException {
+        Map<String, String> allSources = new LinkedHashMap<>();
+        allSources.putAll(expressionSources);
+        allSources.putAll(filterSources);
+
+        if (allSources.isEmpty()) {
+            log.info("No MAL sources to compile.");
+            return;
+        }
+
+        // Write .java files
+        String packageDir = GENERATED_PACKAGE.replace('.', File.separatorChar);
+        File srcPkgDir = new File(sourceDir, packageDir);
+        if (!srcPkgDir.exists() && !srcPkgDir.mkdirs()) {
+            throw new IOException("Failed to create source dir: " + srcPkgDir);
+        }
+        if (!outputDir.exists() && !outputDir.mkdirs()) {
+            throw new IOException("Failed to create output dir: " + outputDir);
+        }
+
+        List<File> javaFiles = new ArrayList<>();
+        for (Map.Entry<String, String> entry : allSources.entrySet()) {
+            File javaFile = new File(srcPkgDir, entry.getKey() + ".java");
+            Files.writeString(javaFile.toPath(), entry.getValue());
+            javaFiles.add(javaFile);
+        }
+
+        // Compile
+        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+        if (compiler == null) {
+            throw new IllegalStateException("No Java compiler available — requires JDK");
+        }
+
+        StringWriter errorWriter = new StringWriter();
+
+        try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) {
+            Iterable<? extends JavaFileObject> compilationUnits =
+                fileManager.getJavaFileObjectsFromFiles(javaFiles);
+
+            List<String> options = Arrays.asList(
+                "-d", outputDir.getAbsolutePath(),
+                "-classpath", classpath
+            );
+
+            JavaCompiler.CompilationTask task = compiler.getTask(
+                errorWriter, fileManager, null, options, null, compilationUnits);
+
+            boolean success = task.call();
+            if (!success) {
+                throw new RuntimeException(
+                    "Java compilation failed for " + javaFiles.size() + " MAL sources:\n"
+                        + errorWriter);
+            }
+        }
+
+        log.info("Compiled {} MAL sources to {}", allSources.size(), outputDir);
+    }
+
+    /**
+     * Write mal-expressions.txt manifest: one FQCN per line.
+     */
+    public void writeExpressionManifest(File outputDir) throws IOException {
+        File manifestDir = new File(outputDir, "META-INF");
+        if (!manifestDir.exists() && !manifestDir.mkdirs()) {
+            throw new IOException("Failed to create META-INF dir: " + manifestDir);
+        }
+
+        List<String> lines = expressionSources.keySet().stream()
+            .map(name -> GENERATED_PACKAGE + "." + name)
+            .collect(Collectors.toList());
+        Files.write(new File(manifestDir, "mal-expressions.txt").toPath(), lines);
+        log.info("Wrote mal-expressions.txt with {} entries", lines.size());
+    }
+
+    /**
+     * Write mal-filter-expressions.properties manifest: literal=FQCN.
+     */
+    public void writeFilterManifest(File outputDir) throws IOException {
+        File manifestDir = new File(outputDir, "META-INF");
+        if (!manifestDir.exists() && !manifestDir.mkdirs()) {
+            throw new IOException("Failed to create META-INF dir: " + manifestDir);
+        }
+
+        List<String> lines = filterLiteralToClass.entrySet().stream()
+            .map(e -> escapeProperties(e.getKey()) + "=" + e.getValue())
+            .collect(Collectors.toList());
+        Files.write(new File(manifestDir, "mal-filter-expressions.properties").toPath(), lines);
+        log.info("Wrote mal-filter-expressions.properties with {} entries", lines.size());
+    }
+
+    /** Escape a properties key (= and : need escaping). */
+    private static String escapeProperties(String s) {
+        return s.replace("\\", "\\\\")
+                .replace("=", "\\=")
+                .replace(":", "\\:")
+                .replace(" ", "\\ ");
+    }
+
+    // ---- Argument Utilities ----
+
+    private ArgumentListExpression toArgList(Expression args) {
+        if (args instanceof ArgumentListExpression) {
+            return (ArgumentListExpression) args;
+        }
+        if (args instanceof TupleExpression) {
+            ArgumentListExpression ale = new ArgumentListExpression();
+            for (Expression e : ((TupleExpression) args).getExpressions()) {
+                ale.addExpression(e);
+            }
+            return ale;
+        }
+        ArgumentListExpression ale = new ArgumentListExpression();
+        ale.addExpression(args);
+        return ale;
+    }
+
+    private List<String> visitArgList(ArgumentListExpression args) {
+        List<String> result = new ArrayList<>();
+        for (Expression arg : args.getExpressions()) {
+            result.add(visitExpression(arg));
+        }
+        return result;
+    }
+
+    // ---- Utility ----
+
+    static String escapeJava(String s) {
+        return s.replace("\\", "\\\\")
+                .replace("\"", "\\\"")
+                .replace("\n", "\\n")
+                .replace("\r", "\\r")
+                .replace("\t", "\\t");
+    }
+}
diff --git a/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/server/buildtools/precompiler/Precompiler.java b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/server/buildtools/precompiler/Precompiler.java
index db3b0d2..6e7c2f1 100644
--- a/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/server/buildtools/precompiler/Precompiler.java
+++ b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/server/buildtools/precompiler/Precompiler.java
@@ -197,6 +197,9 @@ public class Precompiler {
         // ---- LAL pre-compilation ----
         compileLAL(outputDir);
 
+        // ---- GraalVM native-image metadata generation ----
+        generateNativeImageConfig(outputDir);
+
         log.info("Precompiler: done");
     }
 
@@ -299,6 +302,9 @@ public class Precompiler {
             DSL.getScriptRegistry().size(),
             FilterExpression.getScriptRegistry().size());
 
+        // ---- MAL-to-Java transpilation ----
+        transpileMAL(outputDir);
+
         // ---- Serialize config data as JSON for runtime loaders ----
         serializeMALConfigData(outputDir, rulesByPath);
     }
@@ -389,6 +395,69 @@ public class Precompiler {
         return loadedRules;
     }
 
+    /**
+     * Transpile MAL expressions and filters from Groovy to pure Java classes.
+     * Runs after MetricConvert pipeline — uses expression strings captured by DSL
+     * and filter literals captured by FilterExpression.
+     */
+    private static void transpileMAL(String outputDir) throws Exception {
+        MalToJavaTranspiler transpiler = new MalToJavaTranspiler();
+        int exprCount = 0;
+        int filterCount = 0;
+
+        // Transpile expressions: scriptName → expression string
+        for (Map.Entry<String, String> entry : DSL.getExpressionRegistry().entrySet()) {
+            String scriptName = entry.getKey();
+            String expression = entry.getValue();
+            String className = "MalExpr_" + scriptName;
+            try {
+                String source = transpiler.transpileExpression(className, expression);
+                transpiler.registerExpression(className, source);
+                exprCount++;
+            } catch (Exception e) {
+                log.warn("Failed to transpile MAL expression: {} = {}", scriptName, expression, e);
+            }
+        }
+
+        // Transpile filters: literal → Groovy class (we need literal for transpiler)
+        int filterIdx = 0;
+        for (String literal : FilterExpression.getScriptRegistry().keySet()) {
+            String className = "MalFilter_" + filterIdx++;
+            try {
+                String source = transpiler.transpileFilter(className, literal);
+                transpiler.registerFilter(className, literal, source);
+                filterCount++;
+            } catch (Exception e) {
+                log.warn("Failed to transpile MAL filter: {}", literal, e);
+            }
+        }
+
+        // Compile generated Java sources.
+        // Transpiled code targets GraalVM APIs (Java functional interfaces in SampleFamily,
+        // public ExpressionParsingContext.get()). Prepend meter-analyzer-for-graalvm classes
+        // to javac classpath so they take precedence over upstream meter-analyzer.
+        File javaSourceDir = new File(outputDir, "generated-mal-sources");
+        File javaOutputDir = new File(outputDir);
+        String classpath = resolveClasspath();
+        File graalvmMeterClasses = findGraalvmMeterClasses(outputDir);
+        if (graalvmMeterClasses != null) {
+            classpath = graalvmMeterClasses.getAbsolutePath() + File.pathSeparator + classpath;
+            log.info("MAL transpilation: prepended {} to javac classpath",
+                graalvmMeterClasses.getAbsolutePath());
+        } else {
+            log.warn("MAL transpilation: meter-analyzer-for-graalvm classes not found; "
+                + "transpiled sources may fail to compile");
+        }
+        transpiler.compileAll(javaSourceDir, javaOutputDir, classpath);
+
+        // Write transpiled manifests (alongside existing Groovy manifests)
+        transpiler.writeExpressionManifest(new File(outputDir));
+        transpiler.writeFilterManifest(new File(outputDir));
+
+        log.info("MAL transpilation: {} expressions, {} filters transpiled to Java",
+            exprCount, filterCount);
+    }
+
     /**
      * Pre-compile all LAL (Log Analysis Language) scripts.
      * Uses the same CompilerConfiguration as upstream (CompileStatic + LALPrecompiledExtension,
@@ -678,4 +747,260 @@ public class Precompiler {
     private static void writeManifest(Path path, List<String> lines) throws IOException {
         Files.write(path, lines, StandardCharsets.UTF_8);
     }
+
+    /**
+     * Generate GraalVM native-image metadata (reflect-config.json and resource-config.json)
+     * from the manifests already produced by the precompiler.
+     *
+     * This gives native-image 100% coverage of all pre-compiled classes — unlike the tracing
+     * agent which only captures code paths exercised during a traced run.
+     */
+    private static void generateNativeImageConfig(String outputDir) throws IOException {
+        Path nativeImageDir = Path.of(outputDir,
+            "META-INF", "native-image", "org.apache.skywalking", "oap-graalvm-distro");
+        Files.createDirectories(nativeImageDir);
+
+        generateReflectConfig(outputDir, nativeImageDir);
+        generateResourceConfig(nativeImageDir);
+    }
+
+    /**
+     * Generate reflect-config.json from all manifest files.
+     *
+     * Annotation-scanned classes get full reflection access (fields, methods, constructors)
+     * because AnnotationScan, StreamAnnotationListener, etc. inspect annotations and fields.
+     *
+     * Script/metric/dispatcher classes get constructor-only access since they are loaded
+     * via Class.forName() + getDeclaredConstructor().newInstance().
+     */
+    private static void generateReflectConfig(String outputDir, Path nativeImageDir) throws IOException {
+        Path metaInf = Path.of(outputDir, "META-INF");
+        Path annotationScanDir = metaInf.resolve("annotation-scan");
+
+        List<Map<String, Object>> entries = new ArrayList<>();
+
+        // Annotation-scanned classes — full reflection access
+        String[] fullAccessManifests = {
+            "ScopeDeclaration.txt", "Stream.txt", "Disable.txt", "MultipleDisable.txt",
+            "SourceDispatcher.txt", "ISourceDecorator.txt"
+        };
+        for (String manifest : fullAccessManifests) {
+            Path file = annotationScanDir.resolve(manifest);
+            if (Files.exists(file)) {
+                for (String className : readClassNames(file)) {
+                    entries.add(fullAccessEntry(className));
+                }
+            }
+        }
+
+        // MeterFunction manifest — key=value format, full access (MeterSystem inspects annotations)
+        Path meterFunctionFile = annotationScanDir.resolve("MeterFunction.txt");
+        if (Files.exists(meterFunctionFile)) {
+            for (String className : readValueFromKeyValue(meterFunctionFile)) {
+                entries.add(fullAccessEntry(className));
+            }
+        }
+
+        // OAL metrics and dispatchers — constructor-only
+        addConstructorEntries(entries, metaInf.resolve("oal-metrics-classes.txt"));
+        addConstructorEntries(entries, metaInf.resolve("oal-dispatcher-classes.txt"));
+
+        // MAL Groovy scripts — key=value format, constructor-only
+        Path malScripts = metaInf.resolve("mal-groovy-scripts.txt");
+        if (Files.exists(malScripts)) {
+            for (String className : readValueFromKeyValue(malScripts)) {
+                entries.add(constructorOnlyEntry(className));
+            }
+        }
+
+        // MAL filter scripts — Properties format, constructor-only
+        Path filterScripts = metaInf.resolve("mal-filter-scripts.properties");
+        if (Files.exists(filterScripts)) {
+            Properties props = new Properties();
+            try (InputStream is = Files.newInputStream(filterScripts)) {
+                props.load(is);
+            }
+            List<String> filterClasses = new ArrayList<>(props.stringPropertyNames().stream()
+                .map(k -> props.getProperty(k))
+                .sorted()
+                .collect(Collectors.toList()));
+            for (String className : filterClasses) {
+                entries.add(constructorOnlyEntry(className));
+            }
+        }
+
+        // MAL transpiled expressions — one FQCN per line, constructor-only
+        addConstructorEntries(entries, metaInf.resolve("mal-expressions.txt"));
+
+        // MAL transpiled filters — Properties format, constructor-only
+        Path transpiledFilters = metaInf.resolve("mal-filter-expressions.properties");
+        if (Files.exists(transpiledFilters)) {
+            Properties tProps = new Properties();
+            try (InputStream is = Files.newInputStream(transpiledFilters)) {
+                tProps.load(is);
+            }
+            for (String className : tProps.stringPropertyNames().stream()
+                    .map(tProps::getProperty).sorted().collect(Collectors.toList())) {
+                entries.add(constructorOnlyEntry(className));
+            }
+        }
+
+        // LAL scripts — key=value format, constructor-only
+        Path lalScripts = metaInf.resolve("lal-scripts-by-hash.txt");
+        if (Files.exists(lalScripts)) {
+            for (String className : readValueFromKeyValue(lalScripts)) {
+                entries.add(constructorOnlyEntry(className));
+            }
+        }
+
+        // MAL meter classes — key=value format, constructor-only
+        Path meterClasses = metaInf.resolve("mal-meter-classes.txt");
+        if (Files.exists(meterClasses)) {
+            for (String className : readValueFromKeyValue(meterClasses)) {
+                entries.add(constructorOnlyEntry(className));
+            }
+        }
+
+        // Write reflect-config.json
+        ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
+        mapper.writeValue(nativeImageDir.resolve("reflect-config.json").toFile(), entries);
+        log.info("Generated reflect-config.json with {} entries", entries.size());
+    }
+
+    /**
+     * Generate resource-config.json for all META-INF resources in the generated JAR.
+     */
+    private static void generateResourceConfig(Path nativeImageDir) throws IOException {
+        Map<String, Object> resourceConfig = new LinkedHashMap<>();
+        Map<String, Object> resources = new LinkedHashMap<>();
+        List<Map<String, String>> includes = new ArrayList<>();
+
+        includes.add(Map.of("pattern", "META-INF/annotation-scan/.*\\.txt"));
+        includes.add(Map.of("pattern", "META-INF/oal-.*\\.txt"));
+        includes.add(Map.of("pattern", "META-INF/mal-.*\\.txt"));
+        includes.add(Map.of("pattern", "META-INF/mal-.*\\.properties"));
+        includes.add(Map.of("pattern", "META-INF/lal-.*\\.txt"));
+        includes.add(Map.of("pattern", "META-INF/config-data/.*\\.json"));
+
+        resources.put("includes", includes);
+        resourceConfig.put("resources", resources);
+
+        ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
+        mapper.writeValue(nativeImageDir.resolve("resource-config.json").toFile(), resourceConfig);
+        log.info("Generated resource-config.json with {} resource patterns", includes.size());
+    }
+
+    private static Map<String, Object> fullAccessEntry(String className) {
+        Map<String, Object> entry = new LinkedHashMap<>();
+        entry.put("name", className);
+        entry.put("allDeclaredFields", true);
+        entry.put("allDeclaredMethods", true);
+        entry.put("allDeclaredConstructors", true);
+        return entry;
+    }
+
+    private static Map<String, Object> constructorOnlyEntry(String className) {
+        Map<String, Object> entry = new LinkedHashMap<>();
+        entry.put("name", className);
+        entry.put("methods", List.of(Map.of("name", "<init>", "parameterTypes", List.of())));
+        return entry;
+    }
+
+    /**
+     * Read class names from a manifest file (one class name per line).
+     */
+    private static List<String> readClassNames(Path file) throws IOException {
+        return Files.readAllLines(file, StandardCharsets.UTF_8).stream()
+            .map(String::trim)
+            .filter(s -> !s.isEmpty())
+            .sorted()
+            .collect(Collectors.toList());
+    }
+
+    /**
+     * Read values from a key=value manifest file. Returns sorted unique values.
+     */
+    private static List<String> readValueFromKeyValue(Path file) throws IOException {
+        return Files.readAllLines(file, StandardCharsets.UTF_8).stream()
+            .map(String::trim)
+            .filter(s -> !s.isEmpty() && s.contains("="))
+            .map(s -> s.substring(s.indexOf('=') + 1))
+            .distinct()
+            .sorted()
+            .collect(Collectors.toList());
+    }
+
+    /**
+     * Read class names from a one-per-line manifest and add constructor-only entries.
+     */
+    private static void addConstructorEntries(List<Map<String, Object>> entries, Path file) throws IOException {
+        if (Files.exists(file)) {
+            for (String className : readClassNames(file)) {
+                entries.add(constructorOnlyEntry(className));
+            }
+        }
+    }
+
+    /**
+     * Resolve the runtime classpath for javac compilation.
+     * exec-maven-plugin creates a URLClassLoader with project dependencies;
+     * extract URLs from it. Falls back to java.class.path system property.
+     */
+    @SuppressWarnings("deprecation")
+    /**
+     * Locate meter-analyzer-for-graalvm compiled classes directory.
+     * Tries multiple strategies: basedir property (set by surefire), outputDir traversal,
+     * and user.dir (CWD). Returns the directory if found, null otherwise.
+     */
+    private static File findGraalvmMeterClasses(String outputDir) {
+        String relPath = "oap-libs-for-graalvm/meter-analyzer-for-graalvm/target/classes";
+
+        // Strategy 1: basedir (set by surefire) → build-tools/precompiler/
+        String basedir = System.getProperty("basedir");
+        if (basedir != null) {
+            File candidate = new File(new File(basedir).getParentFile().getParentFile(), relPath);
+            if (candidate.isDirectory()) {
+                return candidate;
+            }
+        }
+
+        // Strategy 2: outputDir traversal (works when outputDir = target/generated-classes)
+        File candidate = new File(outputDir).getParentFile()  // target/
+            .getParentFile()  // precompiler/
+            .getParentFile()  // build-tools/
+            .getParentFile(); // project root
+        candidate = new File(candidate, relPath);
+        if (candidate.isDirectory()) {
+            return candidate;
+        }
+
+        // Strategy 3: user.dir may be project root
+        candidate = new File(System.getProperty("user.dir"), relPath);
+        if (candidate.isDirectory()) {
+            return candidate;
+        }
+
+        return null;
+    }
+
+    private static String resolveClasspath() {
+        Set<String> paths = new java.util.LinkedHashSet<>();
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        while (cl != null) {
+            if (cl instanceof java.net.URLClassLoader) {
+                for (java.net.URL url : ((java.net.URLClassLoader) cl).getURLs()) {
+                    try {
+                        paths.add(new File(url.toURI()).getAbsolutePath());
+                    } catch (Exception e) {
+                        paths.add(url.getPath());
+                    }
+                }
+            }
+            cl = cl.getParent();
+        }
+        if (!paths.isEmpty()) {
+            return String.join(File.pathSeparator, paths);
+        }
+        return System.getProperty("java.class.path", "");
+    }
 }
diff --git a/build-tools/precompiler/src/test/java/org/apache/skywalking/oap/server/buildtools/precompiler/MalToJavaTranspilerTest.java b/build-tools/precompiler/src/test/java/org/apache/skywalking/oap/server/buildtools/precompiler/MalToJavaTranspilerTest.java
new file mode 100644
index 0000000..0bf4e9a
--- /dev/null
+++ b/build-tools/precompiler/src/test/java/org/apache/skywalking/oap/server/buildtools/precompiler/MalToJavaTranspilerTest.java
@@ -0,0 +1,982 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.server.buildtools.precompiler;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for MalToJavaTranspiler.
+ */
+class MalToJavaTranspilerTest {
+
+    private MalToJavaTranspiler transpiler;
+
+    @BeforeEach
+    void setUp() {
+        transpiler = new MalToJavaTranspiler();
+    }
+
+    // ---- AST Parsing + Simple Variable References ----
+
+    @Test
+    void simpleVariableReference() {
+        String java = transpiler.transpileExpression("MalExpr_test", "metric_name");
+        assertNotNull(java);
+
+        // Verify package and class structure
+        assertTrue(java.contains("package " + MalToJavaTranspiler.GENERATED_PACKAGE),
+            "Should have correct package");
+        assertTrue(java.contains("public class MalExpr_test implements MalExpression"),
+            "Should implement MalExpression");
+        assertTrue(java.contains("public SampleFamily run(Map<String, SampleFamily> samples)"),
+            "Should have run method");
+
+        // Verify sample name tracking in ExpressionParsingContext
+        assertTrue(java.contains("ctx.getSamples().add(\"metric_name\")"),
+            "Should track sample name in parsing context");
+
+        // Verify sample lookup
+        assertTrue(java.contains("samples.getOrDefault(\"metric_name\", SampleFamily.EMPTY)"),
+            "Should look up sample family from map");
+    }
+
+    @Test
+    void downsamplingConstantNotTrackedAsSample() {
+        // DownsamplingType constants like SUM should not be treated as sample names
+        String java = transpiler.transpileExpression("MalExpr_test", "SUM");
+        assertNotNull(java);
+
+        // Should NOT track SUM as a sample name
+        assertTrue(!java.contains("ctx.getSamples().add(\"SUM\")"),
+            "Should not track DownsamplingType constant as sample");
+
+        // Should resolve to DownsamplingType.SUM
+        assertTrue(java.contains("DownsamplingType.SUM"),
+            "Should resolve to DownsamplingType.SUM");
+    }
+
+    @Test
+    void parseToAST_producesModuleNode() {
+        var ast = transpiler.parseToAST("some_metric");
+        assertNotNull(ast, "Should produce a ModuleNode");
+        assertNotNull(ast.getStatementBlock(), "Should have a statement block");
+    }
+
+    @Test
+    void constantString() {
+        // A bare string constant — not typical in MAL but tests ConstantExpression
+        String java = transpiler.transpileExpression("MalExpr_test", "'hello'");
+        assertNotNull(java);
+        assertTrue(java.contains("\"hello\""),
+            "Should convert Groovy string to Java string");
+    }
+
+    @Test
+    void constantNumber() {
+        String java = transpiler.transpileExpression("MalExpr_test", "42");
+        assertNotNull(java);
+        assertTrue(java.contains("42"),
+            "Should preserve number literal");
+    }
+
+    // ---- Method Chains + List Literals + Enum Properties ----
+
+    @Test
+    void simpleMethodChain() {
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric_name.sum(['a', 'b']).service(['svc'], Layer.GENERAL)");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".sum(List.of(\"a\", \"b\"))"),
+            "Should translate ['a','b'] to List.of(\"a\", \"b\")");
+        assertTrue(java.contains(".service(List.of(\"svc\"), Layer.GENERAL)"),
+            "Should translate Layer.GENERAL as enum");
+    }
+
+    @Test
+    void tagEqualChain() {
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "cpu_seconds.tagNotEqual('mode', 'idle').sum(['host']).rate('PT1M')");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".tagNotEqual(\"mode\", \"idle\")"),
+            "Should translate tagNotEqual with string args");
+        assertTrue(java.contains(".sum(List.of(\"host\"))"),
+            "Should translate single-element list");
+        assertTrue(java.contains(".rate(\"PT1M\")"),
+            "Should translate rate with string arg");
+    }
+
+    @Test
+    void downsamplingMethod() {
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.downsampling(SUM)");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".downsampling(DownsamplingType.SUM)"),
+            "Should resolve SUM to DownsamplingType.SUM");
+    }
+
+    @Test
+    void retagByK8sMeta() {
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.retagByK8sMeta('service', K8sRetagType.Pod2Service, 'pod', 'namespace')");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".retagByK8sMeta(\"service\", K8sRetagType.Pod2Service, \"pod\", \"namespace\")"),
+            "Should translate K8sRetagType enum and string args");
+    }
+
+    @Test
+    void histogramPercentile() {
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.sum(['le', 'svc']).histogram().histogram_percentile([50, 75, 90])");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".histogram()"),
+            "Should translate no-arg histogram()");
+        assertTrue(java.contains(".histogram_percentile(List.of(50, 75, 90))"),
+            "Should translate integer list");
+    }
+
+    @Test
+    void sampleNameCollectionThroughChain() {
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "my_metric.sum(['a']).service(['svc'], Layer.GENERAL)");
+        assertNotNull(java);
+
+        assertTrue(java.contains("ctx.getSamples().add(\"my_metric\")"),
+            "Should collect sample name from root of method chain");
+        assertTrue(!java.contains("ctx.getSamples().add(\"a\")"),
+            "Should NOT collect 'a' (it's a string constant arg, not a sample)");
+    }
+
+    @Test
+    void detectPointEnum() {
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.serviceRelation(DetectPoint.CLIENT, ['src'], ['dst'], Layer.MESH_DP)");
+        assertNotNull(java);
+
+        assertTrue(java.contains("DetectPoint.CLIENT"),
+            "Should translate DetectPoint enum");
+        assertTrue(java.contains("Layer.MESH_DP"),
+            "Should translate Layer enum");
+    }
+
+    @Test
+    void enumImportsPresent() {
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.service(['svc'], Layer.GENERAL)");
+        assertNotNull(java);
+
+        assertTrue(java.contains("import org.apache.skywalking.oap.server.core.analysis.Layer;"),
+            "Should import Layer");
+        assertTrue(java.contains("import org.apache.skywalking.oap.server.core.source.DetectPoint;"),
+            "Should import DetectPoint");
+        assertTrue(java.contains("import org.apache.skywalking.oap.meter.analyzer.dsl.tagOpt.K8sRetagType;"),
+            "Should import K8sRetagType");
+    }
+
+    // ---- Binary Arithmetic with Operand-Swap ----
+
+    @Test
+    void sfTimesNumber() {
+        // metric * 100 → samples.getOrDefault(...).multiply(100)
+        String java = transpiler.transpileExpression("MalExpr_test", "metric * 100");
+        assertNotNull(java);
+        assertTrue(java.contains(".multiply(100)"),
+            "SF * N should call .multiply(N)");
+    }
+
+    @Test
+    void sfDivNumber() {
+        String java = transpiler.transpileExpression("MalExpr_test", "metric / 1024");
+        assertNotNull(java);
+        assertTrue(java.contains(".div(1024)"),
+            "SF / N should call .div(N)");
+    }
+
+    @Test
+    void numberMinusSf() {
+        // 100 - metric → metric.minus(100).negative()
+        String java = transpiler.transpileExpression("MalExpr_test", "100 - metric");
+        assertNotNull(java);
+        assertTrue(java.contains(".minus(100).negative()"),
+            "N - SF should produce sf.minus(N).negative()");
+    }
+
+    @Test
+    void numberDivSf() {
+        // 1 / metric → metric.newValue(v -> 1 / v)
+        String java = transpiler.transpileExpression("MalExpr_test", "1 / metric");
+        assertNotNull(java);
+        assertTrue(java.contains(".newValue(v -> 1 / v)"),
+            "N / SF should produce sf.newValue(v -> N / v)");
+    }
+
+    @Test
+    void numberPlusSf() {
+        // 10 + metric → metric.plus(10)
+        String java = transpiler.transpileExpression("MalExpr_test", "10 + metric");
+        assertNotNull(java);
+        assertTrue(java.contains(".plus(10)"),
+            "N + SF should swap to sf.plus(N)");
+    }
+
+    @Test
+    void numberTimesSf() {
+        // 100 * metric → metric.multiply(100)
+        String java = transpiler.transpileExpression("MalExpr_test", "100 * metric");
+        assertNotNull(java);
+        assertTrue(java.contains(".multiply(100)"),
+            "N * SF should swap to sf.multiply(N)");
+    }
+
+    @Test
+    void sfMinusSf() {
+        // metricA - metricB → metricA.minus(metricB)
+        String java = transpiler.transpileExpression("MalExpr_test", "mem_total - mem_avail");
+        assertNotNull(java);
+        assertTrue(java.contains("ctx.getSamples().add(\"mem_total\")"),
+            "Should collect both sample names");
+        assertTrue(java.contains("ctx.getSamples().add(\"mem_avail\")"),
+            "Should collect both sample names");
+        assertTrue(java.contains(".minus("),
+            "SF - SF should call .minus()");
+    }
+
+    @Test
+    void sfDivSfTimesNumber() {
+        // metricA / metricB * 100
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "used_bytes / max_bytes * 100");
+        assertNotNull(java);
+        assertTrue(java.contains(".div("),
+            "Should have .div() for SF / SF");
+        assertTrue(java.contains(".multiply(100)"),
+            "Should have .multiply(100) for result * 100");
+    }
+
+    @Test
+    void nestedParenArithmetic() {
+        // 100 - ((mem_free * 100) / mem_total)
+        // Parens are just AST precedence — this is:
+        //   100 - ( (mem_free * 100) / mem_total )
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "100 - ((mem_free * 100) / mem_total)");
+        assertNotNull(java);
+        // Inner: mem_free.multiply(100) → .div(mem_total) → then 100 - result
+        assertTrue(java.contains(".multiply(100)"),
+            "Should have inner multiply");
+        assertTrue(java.contains(".negative()"),
+            "100 - SF should produce .negative()");
+    }
+
+    @Test
+    void parenthesizedWithMethodChain() {
+        // (metric * 100).tagNotEqual('mode', 'idle').sum(['host']).rate('PT1M')
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "(metric * 100).tagNotEqual('mode', 'idle').sum(['host']).rate('PT1M')");
+        assertNotNull(java);
+        assertTrue(java.contains(".multiply(100)"),
+            "Should have multiply inside parens");
+        assertTrue(java.contains(".tagNotEqual(\"mode\", \"idle\")"),
+            "Should chain tagNotEqual after parens");
+        assertTrue(java.contains(".rate(\"PT1M\")"),
+            "Should chain rate at the end");
+    }
+
+    // ---- tag() Closure — Simple Cases ----
+
+    @Test
+    void tagAssignmentWithStringConcat() {
+        // .tag({tags -> tags.route = 'route/' + tags['route']})
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.route = 'route/' + tags['route']})");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".tag((TagFunction)"),
+            "Should cast closure to TagFunction");
+        assertTrue(java.contains("tags.put(\"route\", \"route/\" + tags.get(\"route\"))"),
+            "Should translate assignment with string concat and subscript read");
+        assertTrue(java.contains("return tags;"),
+            "Should return tags at end of lambda");
+    }
+
+    @Test
+    void tagRemove() {
+        // .tag({tags -> tags.remove('condition')})
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.remove('condition')})");
+        assertNotNull(java);
+
+        assertTrue(java.contains("tags.remove(\"condition\")"),
+            "Should translate remove call");
+    }
+
+    @Test
+    void tagPropertyToProperty() {
+        // .tag({tags -> tags.rs_nm = tags.set})
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.rs_nm = tags.set})");
+        assertNotNull(java);
+
+        assertTrue(java.contains("tags.put(\"rs_nm\", tags.get(\"set\"))"),
+            "Should translate property read on RHS to tags.get()");
+    }
+
+    @Test
+    void tagStringConcatWithPropertyRead() {
+        // .tag({tags -> tags.cluster = 'es::' + tags.cluster})
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.cluster = 'es::' + tags.cluster})");
+        assertNotNull(java);
+
+        assertTrue(java.contains("tags.put(\"cluster\", \"es::\" + tags.get(\"cluster\"))"),
+            "Should translate string concat with property read");
+    }
+
+    @Test
+    void tagClosureLambdaStructure() {
+        // Verify the overall lambda structure
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.x = 'y'})");
+        assertNotNull(java);
+
+        assertTrue(java.contains("(tags -> {"),
+            "Should have lambda opening");
+        assertTrue(java.contains("return tags;"),
+            "Should return tags variable");
+    }
+
+    @Test
+    void tagWithSubscriptWrite() {
+        // .tag({tags -> tags['service_name'] = tags['svc']})
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags['service_name'] = tags['svc']})");
+        assertNotNull(java);
+
+        assertTrue(java.contains("tags.put(\"service_name\", tags.get(\"svc\"))"),
+            "Should translate subscript write and read");
+    }
+
+    @Test
+    void tagChainAfterTag() {
+        // .tag({...}).sum(['host']).service(['svc'], Layer.GENERAL)
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.x = 'y'}).sum(['host']).service(['svc'], Layer.GENERAL)");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".tag((TagFunction)"),
+            "Should have tag call");
+        assertTrue(java.contains(".sum(List.of(\"host\"))"),
+            "Should chain sum after tag");
+        assertTrue(java.contains(".service(List.of(\"svc\"), Layer.GENERAL)"),
+            "Should chain service after sum");
+    }
+
+    // ---- tag() Closure — if/else + Compound Conditions ----
+
+    @Test
+    void ifOnlyWithChainedOr() {
+        // Real pattern from otel-rules/oap.yaml: GC type classification
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> if (tags['gc'] == 'PS Scavenge' || tags['gc'] == 'Copy' || tags['gc'] == 'ParNew' || tags['gc'] == 'G1 Young Generation') {tags.gc = 'young_gc_count'} })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("if (\"PS Scavenge\".equals(tags.get(\"gc\"))"),
+            "Should translate first == with constant on left for null-safety");
+        assertTrue(java.contains("|| \"Copy\".equals(tags.get(\"gc\"))"),
+            "Should chain || for second comparison");
+        assertTrue(java.contains("|| \"ParNew\".equals(tags.get(\"gc\"))"),
+            "Should chain || for third comparison");
+        assertTrue(java.contains("|| \"G1 Young Generation\".equals(tags.get(\"gc\"))"),
+            "Should chain || for fourth comparison");
+        assertTrue(java.contains("tags.put(\"gc\", \"young_gc_count\")"),
+            "Should translate assignment in if body");
+    }
+
+    @Test
+    void ifElse() {
+        // Real pattern from elasticsearch-index.yaml: primary/replica classification
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> if (tags['primary'] == 'true') {tags.primary = 'primary'} else {tags.primary = 'replica'} })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("if (\"true\".equals(tags.get(\"primary\"))"),
+            "Should translate == comparison in condition");
+        assertTrue(java.contains("tags.put(\"primary\", \"primary\")"),
+            "Should translate if-branch assignment");
+        assertTrue(java.contains("} else {"),
+            "Should have else clause");
+        assertTrue(java.contains("tags.put(\"primary\", \"replica\")"),
+            "Should translate else-branch assignment");
+    }
+
+    @Test
+    void ifOnlyNoElse() {
+        // if-only, no else — the else block should be empty
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> if (tags['level'] == '1') {tags.level = 'L1 aggregation'} })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("if (\"1\".equals(tags.get(\"level\"))"),
+            "Should translate condition");
+        assertTrue(java.contains("tags.put(\"level\", \"L1 aggregation\")"),
+            "Should translate if-body");
+        assertTrue(!java.contains("else"),
+            "Should NOT have else clause");
+    }
+
+    @Test
+    void notEqualComparison() {
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> if (tags['status'] != 'ok') {tags.status = 'error'} })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("!\"ok\".equals(tags.get(\"status\"))"),
+            "Should translate != with negated .equals()");
+    }
+
+    @Test
+    void logicalAndCondition() {
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> if (tags['a'] == 'x' && tags['b'] == 'y') {tags.c = 'z'} })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("\"x\".equals(tags.get(\"a\")) && \"y\".equals(tags.get(\"b\"))"),
+            "Should translate && with .equals() on both sides");
+    }
+
+    @Test
+    void chainedTagClosuresWithIf() {
+        // Real pattern from oap.yaml: two chained tag closures
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> if (tags['level'] == '1') {tags.level = 'L1 aggregation'} })" +
+            ".tag({tags -> if (tags['level'] == '2') {tags.level = 'L2 aggregation'} })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("\"1\".equals(tags.get(\"level\"))"),
+            "Should translate first tag closure condition");
+        assertTrue(java.contains("\"2\".equals(tags.get(\"level\"))"),
+            "Should translate second tag closure condition");
+    }
+
+    @Test
+    void ifWithMethodChainAfter() {
+        // tag closure with if, followed by method chain
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> if (tags['gc'] == 'Copy') {tags.gc = 'young'} }).sum(['host']).service(['svc'], Layer.GENERAL)");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".tag((TagFunction)"),
+            "Should have TagFunction cast");
+        assertTrue(java.contains("\"Copy\".equals(tags.get(\"gc\"))"),
+            "Should have if condition");
+        assertTrue(java.contains(".sum(List.of(\"host\"))"),
+            "Should chain sum after tag");
+    }
+
+    // ---- Filter Closures ----
+
+    @Test
+    void simpleEqualityFilter() {
+        // Real pattern: { tags -> tags.job_name == 'vm-monitoring' }
+        String java = transpiler.transpileFilter("MalFilter_0",
+            "{ tags -> tags.job_name == 'vm-monitoring' }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("public class MalFilter_0 implements MalFilter"),
+            "Should implement MalFilter");
+        assertTrue(java.contains("public boolean test(Map<String, String> tags)"),
+            "Should have test method");
+        assertTrue(java.contains("\"vm-monitoring\".equals(tags.get(\"job_name\"))"),
+            "Should translate == with constant on left");
+    }
+
+    @Test
+    void filterPackageAndImports() {
+        String java = transpiler.transpileFilter("MalFilter_0",
+            "{ tags -> tags.job_name == 'x' }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("package " + MalToJavaTranspiler.GENERATED_PACKAGE),
+            "Should have correct package");
+        assertTrue(java.contains("import java.util.*;"),
+            "Should import java.util");
+        assertTrue(java.contains("import org.apache.skywalking.oap.meter.analyzer.dsl.*;"),
+            "Should import dsl package");
+    }
+
+    @Test
+    void orFilter() {
+        // Real pattern: tags.job_name == 'x' || tags.job_name == 'y'
+        String java = transpiler.transpileFilter("MalFilter_1",
+            "{ tags -> tags.job_name == 'flink-jobManager-monitoring' || tags.job_name == 'flink-taskManager-monitoring' }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("\"flink-jobManager-monitoring\".equals(tags.get(\"job_name\"))"),
+            "Should translate first ==");
+        assertTrue(java.contains("|| \"flink-taskManager-monitoring\".equals(tags.get(\"job_name\"))"),
+            "Should translate || with second ==");
+    }
+
+    @Test
+    void inListFilter() {
+        // Real pattern: tags.job_name in ['kubernetes-cadvisor', 'kube-state-metrics']
+        String java = transpiler.transpileFilter("MalFilter_2",
+            "{ tags -> tags.job_name in ['kubernetes-cadvisor', 'kube-state-metrics'] }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("List.of(\"kubernetes-cadvisor\", \"kube-state-metrics\").contains(tags.get(\"job_name\"))"),
+            "Should translate 'in' to List.of().contains()");
+    }
+
+    @Test
+    void compoundAndFilter() {
+        // Real pattern: tags.cloud_provider == 'aws' && tags.Namespace == 'AWS/S3'
+        String java = transpiler.transpileFilter("MalFilter_3",
+            "{ tags -> tags.cloud_provider == 'aws' && tags.Namespace == 'AWS/S3' }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("\"aws\".equals(tags.get(\"cloud_provider\"))"),
+            "Should translate first ==");
+        assertTrue(java.contains("&& \"AWS/S3\".equals(tags.get(\"Namespace\"))"),
+            "Should translate && with second ==");
+    }
+
+    @Test
+    void truthinessFilter() {
+        // Real pattern: tags.ApiId used as boolean (truthiness)
+        String java = transpiler.transpileFilter("MalFilter_4",
+            "{ tags -> tags.cloud_provider == 'aws' && tags.Stage }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("\"aws\".equals(tags.get(\"cloud_provider\"))"),
+            "Should translate ==");
+        assertTrue(java.contains("(tags.get(\"Stage\") != null && !tags.get(\"Stage\").isEmpty())"),
+            "Should translate bare tags.Stage as truthiness check");
+    }
+
+    @Test
+    void negatedTruthinessFilter() {
+        // Real pattern: !tags.Method — negated truthiness
+        String java = transpiler.transpileFilter("MalFilter_5",
+            "{ tags -> tags.cloud_provider == 'aws' && !tags.Method }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("(tags.get(\"Method\") == null || tags.get(\"Method\").isEmpty())"),
+            "Should translate !tags.Method as negated truthiness");
+    }
+
+    @Test
+    void compoundWithTruthinessAndNegation() {
+        // Real AWS pattern: compound with ==, truthiness, and negation
+        String java = transpiler.transpileFilter("MalFilter_6",
+            "{ tags -> tags.cloud_provider == 'aws' && tags.Namespace == 'AWS/ApiGateway' && tags.Stage && !tags.Method }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("\"aws\".equals(tags.get(\"cloud_provider\"))"),
+            "Should translate first ==");
+        assertTrue(java.contains("\"AWS/ApiGateway\".equals(tags.get(\"Namespace\"))"),
+            "Should translate second ==");
+        assertTrue(java.contains("(tags.get(\"Stage\") != null && !tags.get(\"Stage\").isEmpty())"),
+            "Should translate truthiness");
+        assertTrue(java.contains("(tags.get(\"Method\") == null || tags.get(\"Method\").isEmpty())"),
+            "Should translate negated truthiness");
+    }
+
+    @Test
+    void wrappedBlockFilter() {
+        // Real pattern: { tags -> {compound} } — inner {} needs unwrapping
+        String java = transpiler.transpileFilter("MalFilter_7",
+            "{ tags -> {tags.cloud_provider == 'aws' && tags.Namespace == 'AWS/S3'} }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("\"aws\".equals(tags.get(\"cloud_provider\"))"),
+            "Should unwrap inner block and translate ==");
+        assertTrue(java.contains("\"AWS/S3\".equals(tags.get(\"Namespace\"))"),
+            "Should translate second == after unwrapping");
+    }
+
+    @Test
+    void truthinessWithOrInParens() {
+        // Real pattern: (tags.ApiId || tags.ApiName)
+        String java = transpiler.transpileFilter("MalFilter_8",
+            "{ tags -> tags.cloud_provider == 'aws' && (tags.ApiId || tags.ApiName) }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("(tags.get(\"ApiId\") != null && !tags.get(\"ApiId\").isEmpty())"),
+            "Should translate ApiId truthiness");
+        assertTrue(java.contains("(tags.get(\"ApiName\") != null && !tags.get(\"ApiName\").isEmpty())"),
+            "Should translate ApiName truthiness");
+    }
+
+    // ---- forEach() Closure ----
+
+    @Test
+    void forEachBasicStructure() {
+        // Simple forEach with tag write
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['client', 'server'], { prefix, tags -> tags[prefix + '_id'] = 'test' })");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".forEach(List.of(\"client\", \"server\"), (ForEachFunction)"),
+            "Should cast closure to ForEachFunction");
+        assertTrue(java.contains("(prefix, tags) -> {"),
+            "Should have two-parameter lambda");
+        assertTrue(java.contains("tags.put(prefix + \"_id\", \"test\")"),
+            "Should translate dynamic subscript write");
+    }
+
+    @Test
+    void forEachNullCheckWithEarlyReturn() {
+        // Real pattern 1: null check with early return
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['client'], { prefix, tags -> if (tags[prefix + '_process_id'] != null) { return } })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("tags.get(prefix + \"_process_id\") != null"),
+            "Should translate null check");
+        assertTrue(java.contains("return;"),
+            "Should have void return for early exit");
+    }
+
+    @Test
+    void forEachWithProcessRegistry() {
+        // Real pattern: static method call with tag reads
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['client'], { prefix, tags -> " +
+            "tags[prefix + '_process_id'] = ProcessRegistry.generateVirtualLocalProcess(tags.service, tags.instance) })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("ProcessRegistry.generateVirtualLocalProcess(tags.get(\"service\"), tags.get(\"instance\"))"),
+            "Should translate static method call with tag reads as args");
+        assertTrue(java.contains("tags.put(prefix + \"_process_id\","),
+            "Should translate dynamic subscript write");
+    }
+
+    @Test
+    void forEachVarDeclaration() {
+        // Variable declaration inside forEach
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['component'], { key, tags -> String result = '' })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("String result = \"\""),
+            "Should translate variable declaration with empty string");
+    }
+
+    @Test
+    void forEachVarDeclWithTagRead() {
+        // Variable declaration with tag value initialization
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['component'], { key, tags -> String protocol = tags['protocol'] })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("String protocol = tags.get(\"protocol\")"),
+            "Should translate var decl with tag read");
+    }
+
+    @Test
+    void forEachIfElseIfChain() {
+        // Real pattern 2: chained if/else-if/else with local variables
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['component'], { key, tags -> " +
+            "String protocol = tags['protocol']\n" +
+            "String ssl = tags['is_ssl']\n" +
+            "String result = ''\n" +
+            "if (protocol == 'http' && ssl == 'true') { result = '129' } " +
+            "else if (protocol == 'http') { result = '49' } " +
+            "else if (ssl == 'true') { result = '130' } " +
+            "else { result = '110' }\n" +
+            "tags[key] = result })");
+        assertNotNull(java);
+
+        // Variable declarations
+        assertTrue(java.contains("String protocol = tags.get(\"protocol\")"),
+            "Should declare protocol");
+        assertTrue(java.contains("String ssl = tags.get(\"is_ssl\")"),
+            "Should declare ssl");
+
+        // If condition with local vars
+        assertTrue(java.contains("\"http\".equals(protocol)"),
+            "Should compare local var with .equals()");
+        assertTrue(java.contains("\"true\".equals(ssl)"),
+            "Should compare ssl with .equals()");
+
+        // Else-if chain (not nested else { if ... })
+        assertTrue(java.contains("} else if ("),
+            "Should produce else-if, not nested else { if }");
+
+        // Final else
+        assertTrue(java.contains("} else {"),
+            "Should have final else");
+        assertTrue(java.contains("result = \"110\""),
+            "Should assign default value in else");
+
+        // Tag write with variable key
+        assertTrue(java.contains("tags.put(key, result)"),
+            "Should write result to tags[key]");
+    }
+
+    @Test
+    void forEachLocalVarAssignment() {
+        // Local variable reassignment
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['x'], { key, tags -> " +
+            "String r = ''\n" +
+            "r = 'abc'\n" +
+            "tags[key] = r })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("r = \"abc\""),
+            "Should translate local var reassignment");
+    }
+
+    @Test
+    void forEachEqualsOnStringComparison() {
+        // Equality with dynamic subscript in condition
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['client'], { prefix, tags -> " +
+            "if (tags[prefix + '_local'] == 'true') { tags[prefix + '_id'] = 'local' } })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("\"true\".equals(tags.get(prefix + \"_local\"))"),
+            "Should translate dynamic subscript comparison with .equals()");
+        assertTrue(java.contains("tags.put(prefix + \"_id\", \"local\")"),
+            "Should translate dynamic subscript assignment");
+    }
+
+    @Test
+    void chainedForEach() {
+        // Two forEach calls chained
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['a'], { k1, tags -> tags[k1] = 'x' })" +
+            ".forEach(['b'], { k2, tags -> tags[k2] = 'y' })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("(ForEachFunction) (k1, tags)"),
+            "Should have first forEach with k1");
+        assertTrue(java.contains("(ForEachFunction) (k2, tags)"),
+            "Should have second forEach with k2");
+    }
+
+    // ---- Elvis (?:), Safe Navigation (?.), Ternary (? :) ----
+
+    @Test
+    void safeNavigation() {
+        // tags['skywalking_service']?.trim()
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.svc = tags['skywalking_service']?.trim() })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("(tags.get(\"skywalking_service\") != null ? tags.get(\"skywalking_service\").trim() : null)"),
+            "Should translate ?.trim() to null-checked call");
+    }
+
+    @Test
+    void elvisOperator() {
+        // expr ?: 'default'
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.svc = tags['name'] ?: 'unknown' })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("(tags.get(\"name\") != null ? tags.get(\"name\") : \"unknown\")"),
+            "Should translate ?: to null-check with default");
+    }
+
+    @Test
+    void safeNavPlusElvis() {
+        // Real pattern: tags['skywalking_service']?.trim()?:'APISIX'
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.service_name = 'APISIX::'+(tags['skywalking_service']?.trim()?:'APISIX') })");
+        assertNotNull(java);
+
+        // The safe nav produces a null-checked trim
+        assertTrue(java.contains("tags.get(\"skywalking_service\") != null ? tags.get(\"skywalking_service\").trim() : null"),
+            "Should have safe nav for trim");
+        // The elvis wraps it with default
+        assertTrue(java.contains("!= null ?") && java.contains(": \"APISIX\""),
+            "Should have elvis default to APISIX");
+        // String concat prefix
+        assertTrue(java.contains("\"APISIX::\" + "),
+            "Should have string prefix concatenation");
+    }
+
+    @Test
+    void ternaryOperator() {
+        // Real pattern: tags.ApiId ? exprA : exprB
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.service_name = tags.ApiId ? 'gw::'+tags.ApiId : 'gw::'+tags.ApiName })");
+        assertNotNull(java);
+
+        // Condition should be truthiness check
+        assertTrue(java.contains("tags.get(\"ApiId\") != null && !tags.get(\"ApiId\").isEmpty()"),
+            "Should translate ternary condition as truthiness check");
+        // True branch
+        assertTrue(java.contains("\"gw::\" + tags.get(\"ApiId\")"),
+            "Should have true branch expression");
+        // False branch
+        assertTrue(java.contains("\"gw::\" + tags.get(\"ApiName\")"),
+            "Should have false branch expression");
+    }
+
+    @Test
+    void safeNavInFilterCondition() {
+        // Real pattern: tags.Service?.trim() in filter
+        String java = transpiler.transpileFilter("MalFilter_test",
+            "{ tags -> tags.job_name == 'eks-monitoring' && tags.Service?.trim() }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("\"eks-monitoring\".equals(tags.get(\"job_name\"))"),
+            "Should translate == comparison");
+        // tags.Service?.trim() in boolean context → truthiness of the safe nav result
+        assertTrue(java.contains("tags.get(\"Service\") != null ? tags.get(\"Service\").trim() : null"),
+            "Should have safe nav for Service?.trim()");
+    }
+
+    // ---- instance() with PropertiesExtractor, MapExpression ----
+
+    @Test
+    void instanceWithPropertiesExtractor() {
+        // Real pattern: instance([...], '::', [...], '', Layer.K8S_SERVICE, {tags -> ['pod': tags.pod]})
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.instance(['cluster', 'service'], '::', ['pod'], '', Layer.K8S_SERVICE, " +
+            "{tags -> ['pod': tags.pod, 'namespace': tags.namespace]})");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".instance("),
+            "Should have instance call");
+        assertTrue(java.contains("(PropertiesExtractor)"),
+            "Should cast closure to PropertiesExtractor");
+        assertTrue(java.contains("Map.of(\"pod\", tags.get(\"pod\"), \"namespace\", tags.get(\"namespace\"))"),
+            "Should translate map literal to Map.of()");
+    }
+
+    @Test
+    void mapExpressionInTagValue() {
+        // Map literal standalone
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.instance(['svc'], ['inst'], Layer.GENERAL, {tags -> ['key': tags.val]})");
+        assertNotNull(java);
+
+        assertTrue(java.contains("Map.of(\"key\", tags.get(\"val\"))"),
+            "Should translate single-entry map");
+        assertTrue(java.contains("(PropertiesExtractor)"),
+            "Should have PropertiesExtractor cast");
+    }
+
+    @Test
+    void processRelationNoClosures() {
+        // processRelation is just a regular method call — no special handling needed
+        String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.processRelation('side', ['service'], ['instance'], " +
+            "'client_process_id', 'server_process_id', 'component')");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".processRelation(\"side\", List.of(\"service\"), List.of(\"instance\"), " +
+            "\"client_process_id\", \"server_process_id\", \"component\")"),
+            "Should translate processRelation as regular method call");
+    }
+
+    // ---- Compilation + Manifests ----
+
+    @Test
+    void sourceWrittenForCompilation(@TempDir Path tempDir) throws Exception {
+        // Verify source files are written correctly (compilation needs full classpath
+        // including meter-analyzer-for-graalvm — tested in integration build)
+        String source = transpiler.transpileExpression("MalExpr_compile_test",
+            "metric.sum(['host']).service(['svc'], Layer.GENERAL)");
+        transpiler.registerExpression("MalExpr_compile_test", source);
+
+        File sourceDir = tempDir.resolve("src").toFile();
+        File outputDir = tempDir.resolve("classes").toFile();
+
+        // Try compilation — may fail if runtime classes not on classpath
+        try {
+            transpiler.compileAll(sourceDir, outputDir, System.getProperty("java.class.path"));
+
+            // If compilation succeeds, verify .class file
+            String classPath = MalToJavaTranspiler.GENERATED_PACKAGE.replace('.', File.separatorChar)
+                + File.separator + "MalExpr_compile_test.class";
+            assertTrue(new File(outputDir, classPath).exists(),
+                "Compiled .class file should exist");
+        } catch (RuntimeException e) {
+            if (e.getMessage().contains("compilation failed")) {
+                // Expected when MalExpression etc. not on test classpath
+                // Verify source was written correctly instead
+                String pkgPath = MalToJavaTranspiler.GENERATED_PACKAGE.replace('.', File.separatorChar);
+                File javaFile = new File(sourceDir, pkgPath + "/MalExpr_compile_test.java");
+                assertTrue(javaFile.exists(), "Source .java file should be written");
+                String written = Files.readString(javaFile.toPath());
+                assertTrue(written.contains("implements MalExpression"),
+                    "Written source should implement MalExpression");
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Test
+    void expressionManifest(@TempDir Path tempDir) throws Exception {
+        transpiler.registerExpression("MalExpr_a",
+            transpiler.transpileExpression("MalExpr_a", "metric_a"));
+        transpiler.registerExpression("MalExpr_b",
+            transpiler.transpileExpression("MalExpr_b", "metric_b"));
+
+        File outputDir = tempDir.toFile();
+        transpiler.writeExpressionManifest(outputDir);
+
+        File manifest = new File(outputDir, "META-INF/mal-expressions.txt");
+        assertTrue(manifest.exists(), "Manifest file should exist");
+
+        List<String> lines = Files.readAllLines(manifest.toPath());
+        assertTrue(lines.contains(MalToJavaTranspiler.GENERATED_PACKAGE + ".MalExpr_a"),
+            "Should contain MalExpr_a FQCN");
+        assertTrue(lines.contains(MalToJavaTranspiler.GENERATED_PACKAGE + ".MalExpr_b"),
+            "Should contain MalExpr_b FQCN");
+    }
+
+    @Test
+    void filterManifest(@TempDir Path tempDir) throws Exception {
+        String literal = "{ tags -> tags.job == 'x' }";
+        transpiler.registerFilter("MalFilter_0", literal,
+            transpiler.transpileFilter("MalFilter_0", literal));
+
+        File outputDir = tempDir.toFile();
+        transpiler.writeFilterManifest(outputDir);
+
+        File manifest = new File(outputDir, "META-INF/mal-filter-expressions.properties");
+        assertTrue(manifest.exists(), "Filter manifest should exist");
+
+        String content = Files.readString(manifest.toPath());
+        assertTrue(content.contains(MalToJavaTranspiler.GENERATED_PACKAGE + ".MalFilter_0"),
+            "Should contain MalFilter_0 FQCN");
+    }
+}
diff --git a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/pom.xml b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/pom.xml
index 3441483..2173682 100644
--- a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/pom.xml
+++ b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/pom.xml
@@ -65,6 +65,11 @@
                                 <exclude>org/apache/skywalking/oap/meter/analyzer/dsl/DSL$*.class</exclude>
                                 <exclude>org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression.class</exclude>
                                 <exclude>org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression$*.class</exclude>
+                                <exclude>org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamily.class</exclude>
+                                <exclude>org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamily$*.class</exclude>
+                                <exclude>org/apache/skywalking/oap/meter/analyzer/dsl/ExpressionParsingContext.class</exclude>
+                                <exclude>org/apache/skywalking/oap/meter/analyzer/dsl/ExpressionParsingContext$*.class</exclude>
+                                <exclude>org/apache/skywalking/oap/meter/analyzer/dsl/EntityDescription/InstanceEntityDescription.class</exclude>
                                 <exclude>org/apache/skywalking/oap/meter/analyzer/prometheus/rule/Rules.class</exclude>
                             </excludes>
                         </filter>
diff --git a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/ExpressionParsingContext.java b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/ExpressionParsingContext.java
new file mode 100644
index 0000000..2f8b5a1
--- /dev/null
+++ b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/ExpressionParsingContext.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.meter.analyzer.dsl;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.skywalking.oap.server.core.analysis.meter.ScopeType;
+
+/**
+ * Same-FQCN replacement for ExpressionParsingContext.
+ * Makes get() and create() public so transpiled MalExpression classes
+ * (in package oal.rt.mal) can access them across packages.
+ *
+ * Canonical upstream: meter-analyzer (package-private access).
+ */
+@Getter
+@ToString
+@EqualsAndHashCode
+@Builder
+public class ExpressionParsingContext implements Closeable {
+
+    public static ExpressionParsingContext create() {
+        if (CACHE.get() == null) {
+            CACHE.set(ExpressionParsingContext.builder()
+                                              .samples(Lists.newArrayList())
+                                              .downsampling(DownsamplingType.AVG)
+                                              .scopeLabels(Sets.newHashSet())
+                                              .aggregationLabels(Sets.newHashSet()).build());
+        }
+        return CACHE.get();
+    }
+
+    public static Optional<ExpressionParsingContext> get() {
+        return Optional.ofNullable(CACHE.get());
+    }
+
+    private static final ThreadLocal<ExpressionParsingContext> CACHE = new ThreadLocal<>();
+
+    List<String> samples;
+
+    boolean isHistogram;
+    int[] percentiles;
+
+    Set<String> aggregationLabels;
+
+    Set<String> scopeLabels;
+
+    DownsamplingType downsampling;
+
+    ScopeType scopeType;
+
+    /**
+     * Get labels no scope related.
+     *
+     * @return labels
+     */
+    public List<String> getLabels() {
+        List<String> result = new ArrayList<>(aggregationLabels);
+        result.removeAll(scopeLabels);
+        return result;
+    }
+
+    /**
+     * Validate context after parsing
+     * @param exp expression literal
+     */
+    public void validate(String exp) {
+        Preconditions.checkNotNull(scopeType, exp + ": one of service(), instance() or endpoint() should be invoke");
+    }
+
+    @Override
+    public void close() {
+        CACHE.remove();
+    }
+}
diff --git a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression.java b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression.java
index 7e38792..35252da 100644
--- a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression.java
+++ b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression.java
@@ -79,7 +79,7 @@ public class FilterExpression {
         try {
             Map<String, SampleFamily> result = new HashMap<>();
             for (Map.Entry<String, SampleFamily> entry : sampleFamilies.entrySet()) {
-                SampleFamily afterFilter = entry.getValue().filter(filterClosure);
+                SampleFamily afterFilter = entry.getValue().filter(tags -> filterClosure.call(tags));
                 if (!Objects.equals(afterFilter, SampleFamily.EMPTY)) {
                     result.put(entry.getKey(), afterFilter);
                 }
diff --git a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalExpression.java b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalExpression.java
new file mode 100644
index 0000000..6cdc004
--- /dev/null
+++ b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalExpression.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.meter.analyzer.dsl;
+
+import java.util.Map;
+
+/**
+ * Interface for generated MAL (Meter Analysis Language) expression classes.
+ * Each MAL expression from rule YAML files is transpiled to a pure Java class
+ * implementing this interface at build time.
+ *
+ * Replaces the Groovy DelegatingScript approach — no Groovy runtime needed.
+ */
+@FunctionalInterface
+public interface MalExpression {
+
+    /**
+     * Execute the MAL expression against the given sample families.
+     *
+     * @param samples map of metric name → SampleFamily, replaces Groovy's
+     *                propertyMissing() delegate lookup
+     * @return the resulting SampleFamily, or SampleFamily.EMPTY if no data
+     */
+    SampleFamily run(Map<String, SampleFamily> samples);
+}
diff --git a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalFilter.java b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalFilter.java
new file mode 100644
index 0000000..3096d40
--- /dev/null
+++ b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalFilter.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.meter.analyzer.dsl;
+
+import java.util.Map;
+
+/**
+ * Interface for generated MAL filter expression classes.
+ * Each filter expression from rule YAML files is transpiled to a pure Java class
+ * implementing this interface at build time.
+ *
+ * Replaces the Groovy Closure&lt;Boolean&gt; approach — no Groovy runtime needed.
+ */
+@FunctionalInterface
+public interface MalFilter {
+
+    /**
+     * Test whether samples with the given tags should be included.
+     *
+     * @param tags the sample labels/tags to evaluate
+     * @return true if the sample should be kept, false to filter it out
+     */
+    boolean test(Map<String, String> tags);
+}
diff --git a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamily.java b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamily.java
new file mode 100644
index 0000000..65423bc
--- /dev/null
+++ b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamily.java
@@ -0,0 +1,913 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.meter.analyzer.dsl;
+
+import static java.util.function.UnaryOperator.identity;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.mapping;
+import static java.util.stream.Collectors.toList;
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.skywalking.oap.meter.analyzer.dsl.EntityDescription.EndpointEntityDescription;
+import org.apache.skywalking.oap.meter.analyzer.dsl.EntityDescription.EntityDescription;
+import org.apache.skywalking.oap.meter.analyzer.dsl.EntityDescription.InstanceEntityDescription;
+import org.apache.skywalking.oap.meter.analyzer.dsl.EntityDescription.ProcessEntityDescription;
+import org.apache.skywalking.oap.meter.analyzer.dsl.EntityDescription.ProcessRelationEntityDescription;
+import org.apache.skywalking.oap.meter.analyzer.dsl.EntityDescription.ServiceEntityDescription;
+import org.apache.skywalking.oap.meter.analyzer.dsl.EntityDescription.ServiceRelationEntityDescription;
+import org.apache.skywalking.oap.meter.analyzer.dsl.SampleFamilyFunctions.DecorateFunction;
+import org.apache.skywalking.oap.meter.analyzer.dsl.SampleFamilyFunctions.ForEachFunction;
+import org.apache.skywalking.oap.meter.analyzer.dsl.SampleFamilyFunctions.PropertiesExtractor;
+import org.apache.skywalking.oap.meter.analyzer.dsl.SampleFamilyFunctions.SampleFilter;
+import org.apache.skywalking.oap.meter.analyzer.dsl.SampleFamilyFunctions.TagFunction;
+import org.apache.skywalking.oap.meter.analyzer.dsl.tagOpt.K8sRetagType;
+import org.apache.skywalking.oap.server.core.Const;
+import org.apache.skywalking.oap.server.core.UnexpectedException;
+import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.analysis.meter.MeterEntity;
+import org.apache.skywalking.oap.server.core.analysis.meter.ScopeType;
+import org.apache.skywalking.oap.server.core.source.DetectPoint;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.DoubleBinaryOperator;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import io.vavr.Function2;
+import io.vavr.Function3;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.server.library.util.StringUtil;
+
+/**
+ * GraalVM replacement for upstream SampleFamily.
+ * Original: skywalking/oap-server/analyzer/meter-analyzer/src/main/java/.../dsl/SampleFamily.java
+ * Repackaged into meter-analyzer-for-graalvm via maven-shade-plugin.
+ *
+ * Change: 5 methods changed from groovy.lang.Closure to Java functional interfaces:
+ *   tag(Closure) → tag(TagFunction)
+ *   filter(Closure) → filter(SampleFilter)
+ *   forEach(List, Closure) → forEach(List, ForEachFunction)
+ *   decorate(Closure) → decorate(DecorateFunction)
+ *   instance(..., Closure) → instance(..., PropertiesExtractor)
+ * Why: Groovy Closure is incompatible with GraalVM native image.
+ */
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+@EqualsAndHashCode
+@ToString
+@Slf4j
+public class SampleFamily {
+    public static final SampleFamily EMPTY = new SampleFamily(new Sample[0], RunningContext.EMPTY);
+
+    static SampleFamily build(RunningContext ctx, Sample... samples) {
+        Preconditions.checkNotNull(samples);
+        Preconditions.checkArgument(samples.length > 0);
+        samples = Arrays.stream(samples).filter(sample -> !Double.isNaN(sample.getValue())).toArray(Sample[]::new);
+        if (samples.length == 0) {
+            return EMPTY;
+        }
+        return new SampleFamily(samples, Optional.ofNullable(ctx).orElseGet(RunningContext::instance));
+    }
+
+    public final Sample[] samples;
+
+    public final RunningContext context;
+
+    /* tag filter operations*/
+    public SampleFamily tagEqual(String... labels) {
+        return match(labels, InternalOps::stringComp);
+    }
+
+    public SampleFamily tagNotEqual(String... labels) {
+        return match(labels, (sv, lv) -> !InternalOps.stringComp(sv, lv));
+    }
+
+    public SampleFamily tagMatch(String... labels) {
+        return match(labels, String::matches);
+    }
+
+    public SampleFamily tagNotMatch(String... labels) {
+        return match(labels, (sv, lv) -> !sv.matches(lv));
+    }
+
+    /* value filter operations*/
+    public SampleFamily valueEqual(double compValue) {
+        return valueMatch(CompType.EQUAL, compValue, InternalOps::doubleComp);
+    }
+
+    public SampleFamily valueNotEqual(double compValue) {
+        return valueMatch(CompType.NOT_EQUAL, compValue, InternalOps::doubleComp);
+    }
+
+    public SampleFamily valueGreater(double compValue) {
+        return valueMatch(CompType.GREATER, compValue, InternalOps::doubleComp);
+    }
+
+    public SampleFamily valueGreaterEqual(double compValue) {
+        return valueMatch(CompType.GREATER_EQUAL, compValue, InternalOps::doubleComp);
+    }
+
+    public SampleFamily valueLess(double compValue) {
+        return valueMatch(CompType.LESS, compValue, InternalOps::doubleComp);
+    }
+
+    public SampleFamily valueLessEqual(double compValue) {
+        return valueMatch(CompType.LESS_EQUAL, compValue, InternalOps::doubleComp);
+    }
+
+    /* Binary operator overloading*/
+    public SampleFamily plus(Number number) {
+        return newValue(v -> v + number.doubleValue());
+    }
+
+    public SampleFamily minus(Number number) {
+        return newValue(v -> v - number.doubleValue());
+    }
+
+    public SampleFamily multiply(Number number) {
+        return newValue(v -> v * number.doubleValue());
+    }
+
+    public SampleFamily div(Number number) {
+        return newValue(v -> v / number.doubleValue());
+    }
+
+    public SampleFamily negative() {
+        return newValue(v -> -v);
+    }
+
+    public SampleFamily plus(SampleFamily another) {
+        if (this == EMPTY && another == EMPTY) {
+            return SampleFamily.EMPTY;
+        }
+        if (this == EMPTY) {
+            return another;
+        }
+        if (another == EMPTY) {
+            return this;
+        }
+        return newValue(another, Double::sum);
+    }
+
+    public SampleFamily minus(SampleFamily another) {
+        if (this == EMPTY && another == EMPTY) {
+            return SampleFamily.EMPTY;
+        }
+        if (this == EMPTY) {
+            return another.negative();
+        }
+        if (another == EMPTY) {
+            return this;
+        }
+        return newValue(another, (a, b) -> a - b);
+    }
+
+    public SampleFamily multiply(SampleFamily another) {
+        if (this == EMPTY || another == EMPTY) {
+            return SampleFamily.EMPTY;
+        }
+        return newValue(another, (a, b) -> a * b);
+    }
+
+    public SampleFamily div(SampleFamily another) {
+        if (this == EMPTY) {
+            return SampleFamily.EMPTY;
+        }
+        if (another == EMPTY) {
+            return div(0.0);
+        }
+        return newValue(another, (a, b) -> a / b);
+    }
+
+    /* Aggregation operators */
+    public SampleFamily sum(List<String> by) {
+        return aggregate(by, Double::sum);
+    }
+
+    public SampleFamily max(List<String> by) {
+        return aggregate(by, Double::max);
+    }
+
+    public SampleFamily min(List<String> by) {
+        return aggregate(by, Double::min);
+    }
+
+    public SampleFamily avg(List<String> by) {
+        ExpressionParsingContext.get().ifPresent(ctx -> ctx.aggregationLabels.addAll(by));
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        if (by == null) {
+            double result = Arrays.stream(samples).mapToDouble(Sample::getValue).average().orElse(0.0D);
+            return SampleFamily.build(
+                this.context, InternalOps.newSample(samples[0].name, ImmutableMap.of(), samples[0].timestamp, result));
+        }
+
+        return SampleFamily.build(
+            this.context,
+            Arrays.stream(samples)
+                  .collect(groupingBy(it -> InternalOps.getLabels(by, it), mapping(identity(), toList())))
+                  .entrySet().stream()
+                  .map(entry -> InternalOps.newSample(
+                      entry.getValue().get(0).getName(),
+                      entry.getKey(),
+                      entry.getValue().get(0).getTimestamp(),
+                      entry.getValue().stream().mapToDouble(Sample::getValue).average().orElse(0.0D)
+                  ))
+                  .toArray(Sample[]::new)
+        );
+    }
+
+    public SampleFamily count(List<String> by) {
+        ExpressionParsingContext.get().ifPresent(ctx -> ctx.aggregationLabels.addAll(by));
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        if (by == null) {
+            long result = Arrays.stream(samples).count();
+            return SampleFamily.build(
+                    this.context, InternalOps.newSample(samples[0].name, ImmutableMap.of(), samples[0].timestamp, result));
+        }
+
+        if (by.size() == 1) {
+            Set<String> set = Arrays
+                    .stream(samples)
+                    .map(sample -> sample.labels.get(by.get(0)))
+                    .filter(StringUtils::isNotBlank)
+                    .collect(Collectors.toSet());
+
+            return SampleFamily.build(
+                    this.context, InternalOps.newSample(samples[0].name, ImmutableMap.of(), samples[0].timestamp, set.size()));
+        }
+
+        Stream<Map.Entry<ImmutableMap<String, String>, List<Sample>>> stream = Arrays
+                .stream(samples)
+                .filter(sample -> sample.labels.keySet().containsAll(by))
+                .collect(groupingBy(it -> InternalOps.getLabels(by, it)))
+                .entrySet()
+                .stream()
+                .map(entry -> InternalOps.newSample(
+                        entry.getValue().get(0).getName(),
+                        entry.getKey(),
+                        entry.getValue().get(0).getTimestamp(),
+                        entry.getValue().size()))
+                .collect(groupingBy(it -> InternalOps.groupByExcludedLabel(by.get(by.size() - 1), it), mapping(identity(), toList())))
+                .entrySet()
+                .stream();
+
+        Sample[] array = stream
+                .map(entry -> InternalOps.newSample(
+                        entry.getValue().get(0).getName(),
+                        entry.getKey(),
+                        entry.getValue().get(0).getTimestamp(),
+                        entry.getValue().size()
+                ))
+                .toArray(Sample[]::new);
+
+        SampleFamily sampleFamily = SampleFamily.build(
+                this.context,
+                array
+        );
+        return sampleFamily;
+    }
+
+    protected SampleFamily aggregate(List<String> by, DoubleBinaryOperator aggregator) {
+        ExpressionParsingContext.get().ifPresent(ctx -> ctx.aggregationLabels.addAll(by));
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        if (by == null) {
+            double result = Arrays.stream(samples).mapToDouble(s -> s.value).reduce(aggregator).orElse(0.0D);
+            return SampleFamily.build(
+                this.context, InternalOps.newSample(samples[0].name, ImmutableMap.of(), samples[0].timestamp, result));
+        }
+        return SampleFamily.build(
+            this.context,
+            Arrays.stream(samples)
+                  .collect(groupingBy(it -> InternalOps.getLabels(by, it), mapping(identity(), toList())))
+                  .entrySet().stream()
+                  .map(entry -> InternalOps.newSample(
+                      entry.getValue().get(0).getName(),
+                      entry.getKey(),
+                      entry.getValue().get(0).getTimestamp(),
+                      entry.getValue().stream().mapToDouble(Sample::getValue).reduce(aggregator).orElse(0.0D)
+                  ))
+                  .toArray(Sample[]::new)
+        );
+    }
+
+    /* Function */
+    public SampleFamily increase(String range) {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(range));
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        return SampleFamily.build(
+            this.context,
+            Arrays.stream(samples)
+                  .map(sample -> sample.increase(
+                      range,
+                      context.metricName,
+                      (lowerBoundValue, unused) -> sample.value - lowerBoundValue
+                  ))
+                  .toArray(Sample[]::new)
+        );
+    }
+
+    public SampleFamily rate(String range) {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(range));
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        return SampleFamily.build(
+            this.context,
+            Arrays.stream(samples)
+                  .map(sample -> sample.increase(
+                      range,
+                      context.metricName,
+                      (lowerBoundValue, lowerBoundTime) -> {
+                          final long timeDiff = (sample.timestamp - lowerBoundTime) / 1000;
+                          return timeDiff < 1L ? 0.0 : (sample.value - lowerBoundValue) / timeDiff;
+                      }
+                  ))
+                  .toArray(Sample[]::new)
+        );
+    }
+
+    public SampleFamily irate() {
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        return SampleFamily.build(
+            this.context,
+            Arrays.stream(samples)
+                  .map(sample -> sample.increase(
+                      context.metricName,
+                      (lowerBoundValue, lowerBoundTime) -> {
+                          final long timeDiff = (sample.timestamp - lowerBoundTime) / 1000;
+                          return timeDiff < 1L ? 0.0 : (sample.value - lowerBoundValue) / timeDiff;
+                      }
+                  ))
+                  .toArray(Sample[]::new)
+        );
+    }
+
+    /**
+     * Tag transformation using a Java functional interface.
+     * Replaces upstream tag(Closure) — no Groovy Closure needed.
+     */
+    @SuppressWarnings(value = "unchecked")
+    public SampleFamily tag(TagFunction fn) {
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        return SampleFamily.build(
+            this.context,
+            Arrays.stream(samples)
+                  .map(sample -> {
+                      Map<String, String> arg = Maps.newHashMap(sample.labels);
+                      Map<String, String> result = fn.apply(arg);
+                      return sample.toBuilder()
+                                   .labels(ImmutableMap.copyOf(
+                                       Optional.ofNullable(result).orElse(arg)))
+                                   .build();
+                  }).toArray(Sample[]::new)
+        );
+    }
+
+    /**
+     * Sample filtering using a Java functional interface.
+     * Replaces upstream filter(Closure) — no Groovy Closure needed.
+     */
+    public SampleFamily filter(SampleFilter filter) {
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        final Sample[] filtered = Arrays.stream(samples)
+                                        .filter(it -> filter.test(it.labels))
+                                        .toArray(Sample[]::new);
+        if (filtered.length == 0) {
+            return EMPTY;
+        }
+        return SampleFamily.build(context, filtered);
+    }
+
+    /* k8s retags*/
+    public SampleFamily retagByK8sMeta(String newLabelName,
+                                       K8sRetagType type,
+                                       String existingLabelName,
+                                       String namespaceLabelName) {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(newLabelName));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(existingLabelName));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(namespaceLabelName));
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+
+        return SampleFamily.build(
+            this.context, type.execute(samples, newLabelName, existingLabelName, namespaceLabelName));
+    }
+
+    public SampleFamily histogram() {
+        return histogram("le", this.context.defaultHistogramBucketUnit);
+    }
+
+    public SampleFamily histogram(String le) {
+        return histogram(le, this.context.defaultHistogramBucketUnit);
+    }
+
+    public SampleFamily histogram(String le, TimeUnit unit) {
+        long scale = unit.toMillis(1);
+        Preconditions.checkArgument(scale > 0);
+        ExpressionParsingContext.get().ifPresent(ctx -> ctx.isHistogram = true);
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        return SampleFamily.build(
+            this.context,
+            Stream.concat(
+                Arrays.stream(samples).filter(s -> !s.labels.containsKey(le)),
+                Arrays.stream(samples)
+                      .filter(s -> s.labels.containsKey(le))
+                      .sorted(Comparator.comparingDouble(s -> Double.parseDouble(s.labels.get(le))))
+                      .map(s -> {
+                          double r = s.value;
+                          ImmutableMap<String, String> ll = ImmutableMap.<String, String>builder()
+                                                                        .putAll(Maps.filterKeys(s.labels,
+                                                                                                key -> !Objects.equals(
+                                                                                                    key, le)
+                                                                        ))
+                                                                        .put(
+                                                                            "le",
+                                                                            String.valueOf((long) ((Double.parseDouble(s.labels.get(le))) * scale)))
+                                                                        .build();
+                          return InternalOps.newSample(s.name, ll, s.timestamp, r);
+                      })
+            ).toArray(Sample[]::new)
+        );
+    }
+
+    public SampleFamily histogram_percentile(List<Integer> percentiles) {
+        Preconditions.checkArgument(percentiles.size() > 0);
+        int[] p = percentiles.stream().mapToInt(i -> i).toArray();
+        ExpressionParsingContext.get().ifPresent(ctx -> {
+            Preconditions.checkState(
+                ctx.isHistogram, "histogram() should be invoked before invoking histogram_percentile()");
+            ctx.percentiles = p;
+        });
+        return this;
+    }
+
+    public SampleFamily service(List<String> labelKeys, Layer layer) {
+        Preconditions.checkArgument(labelKeys.size() > 0);
+        ExpressionParsingContext.get().ifPresent(ctx -> {
+            ctx.scopeType = ScopeType.SERVICE;
+            ctx.scopeLabels.addAll(labelKeys);
+        });
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        return createMeterSamples(new ServiceEntityDescription(labelKeys, layer, Const.POINT));
+    }
+
+    public SampleFamily service(List<String> labelKeys, String delimiter, Layer layer) {
+        Preconditions.checkArgument(labelKeys.size() > 0);
+        ExpressionParsingContext.get().ifPresent(ctx -> {
+            ctx.scopeType = ScopeType.SERVICE;
+            ctx.scopeLabels.addAll(labelKeys);
+        });
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        return createMeterSamples(new ServiceEntityDescription(labelKeys, layer, delimiter));
+    }
+
+    /**
+     * Instance binding with properties extractor using a Java functional interface.
+     * Replaces upstream instance(..., Closure) — no Groovy Closure needed.
+     */
+    public SampleFamily instance(List<String> serviceKeys, String serviceDelimiter,
+                                 List<String> instanceKeys, String instanceDelimiter,
+                                 Layer layer, PropertiesExtractor propertiesExtractor) {
+        Preconditions.checkArgument(serviceKeys.size() > 0);
+        Preconditions.checkArgument(instanceKeys.size() > 0);
+        ExpressionParsingContext.get().ifPresent(ctx -> {
+            ctx.scopeType = ScopeType.SERVICE_INSTANCE;
+            ctx.scopeLabels.addAll(serviceKeys);
+            ctx.scopeLabels.addAll(instanceKeys);
+        });
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        return createMeterSamples(new InstanceEntityDescription(
+            serviceKeys, instanceKeys, layer, serviceDelimiter, instanceDelimiter, propertiesExtractor));
+    }
+
+    public SampleFamily instance(List<String> serviceKeys, List<String> instanceKeys, Layer layer) {
+        return instance(serviceKeys, Const.POINT, instanceKeys, Const.POINT, layer, null);
+    }
+
+    public SampleFamily endpoint(List<String> serviceKeys, List<String> endpointKeys, String delimiter, Layer layer) {
+        Preconditions.checkArgument(serviceKeys.size() > 0);
+        Preconditions.checkArgument(endpointKeys.size() > 0);
+        ExpressionParsingContext.get().ifPresent(ctx -> {
+            ctx.scopeType = ScopeType.ENDPOINT;
+            ctx.scopeLabels.addAll(serviceKeys);
+            ctx.scopeLabels.addAll(endpointKeys);
+        });
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        return createMeterSamples(new EndpointEntityDescription(serviceKeys, endpointKeys, layer, delimiter));
+    }
+
+    public SampleFamily endpoint(List<String> serviceKeys, List<String> endpointKeys, Layer layer) {
+        return endpoint(serviceKeys, endpointKeys, Const.POINT, layer);
+    }
+
+    public SampleFamily process(List<String> serviceKeys, List<String> serviceInstanceKeys, List<String> processKeys, String layerKey) {
+        Preconditions.checkArgument(serviceKeys.size() > 0);
+        Preconditions.checkArgument(serviceInstanceKeys.size() > 0);
+        Preconditions.checkArgument(processKeys.size() > 0);
+        ExpressionParsingContext.get().ifPresent(ctx -> {
+            ctx.scopeType = ScopeType.PROCESS;
+            ctx.scopeLabels.addAll(serviceKeys);
+            ctx.scopeLabels.addAll(serviceInstanceKeys);
+            ctx.scopeLabels.addAll(processKeys);
+            ctx.scopeLabels.add(layerKey);
+        });
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        return createMeterSamples(new ProcessEntityDescription(serviceKeys, serviceInstanceKeys, processKeys, layerKey, Const.POINT));
+    }
+
+    public SampleFamily serviceRelation(DetectPoint detectPoint, List<String> sourceServiceKeys, List<String> destServiceKeys, Layer layer) {
+        Preconditions.checkArgument(sourceServiceKeys.size() > 0);
+        Preconditions.checkArgument(destServiceKeys.size() > 0);
+        ExpressionParsingContext.get().ifPresent(ctx -> {
+            ctx.scopeType = ScopeType.SERVICE_RELATION;
+            ctx.scopeLabels.addAll(sourceServiceKeys);
+            ctx.scopeLabels.addAll(destServiceKeys);
+        });
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        return createMeterSamples(new ServiceRelationEntityDescription(sourceServiceKeys, destServiceKeys, detectPoint, layer, Const.POINT, null));
+    }
+
+    public SampleFamily serviceRelation(DetectPoint detectPoint, List<String> sourceServiceKeys, List<String> destServiceKeys, String delimiter, Layer layer, String componentIdKey) {
+        Preconditions.checkArgument(sourceServiceKeys.size() > 0);
+        Preconditions.checkArgument(destServiceKeys.size() > 0);
+        ExpressionParsingContext.get().ifPresent(ctx -> {
+            ctx.scopeType = ScopeType.SERVICE_RELATION;
+            ctx.scopeLabels.addAll(sourceServiceKeys);
+            ctx.scopeLabels.addAll(destServiceKeys);
+            ctx.scopeLabels.add(componentIdKey);
+        });
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        return createMeterSamples(new ServiceRelationEntityDescription(sourceServiceKeys, destServiceKeys, detectPoint, layer, delimiter, componentIdKey));
+    }
+
+    /**
+     * ForEach iteration using a Java functional interface.
+     * Replaces upstream forEach(List, Closure) — no Groovy Closure needed.
+     */
+    public SampleFamily forEach(List<String> array, ForEachFunction each) {
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        return SampleFamily.build(this.context, Arrays.stream(this.samples).map(sample -> {
+            Map<String, String> labels = Maps.newHashMap(sample.getLabels());
+            for (String element : array) {
+                each.accept(element, labels);
+            }
+            return sample.toBuilder().labels(ImmutableMap.copyOf(labels)).build();
+        }).toArray(Sample[]::new));
+    }
+
+    public SampleFamily processRelation(String detectPointKey, List<String> serviceKeys, List<String> instanceKeys, String sourceProcessIdKey, String destProcessIdKey, String componentKey) {
+        Preconditions.checkArgument(serviceKeys.size() > 0);
+        Preconditions.checkArgument(instanceKeys.size() > 0);
+        Preconditions.checkArgument(StringUtil.isNotEmpty(sourceProcessIdKey));
+        Preconditions.checkArgument(StringUtil.isNotEmpty(destProcessIdKey));
+        ExpressionParsingContext.get().ifPresent(ctx -> {
+            ctx.scopeType = ScopeType.PROCESS_RELATION;
+            ctx.scopeLabels.addAll(serviceKeys);
+            ctx.scopeLabels.addAll(instanceKeys);
+            ctx.scopeLabels.add(detectPointKey);
+            ctx.scopeLabels.add(sourceProcessIdKey);
+            ctx.scopeLabels.add(destProcessIdKey);
+            ctx.scopeLabels.add(componentKey);
+        });
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        return createMeterSamples(new ProcessRelationEntityDescription(serviceKeys, instanceKeys, sourceProcessIdKey, destProcessIdKey, detectPointKey, componentKey, Const.POINT));
+    }
+
+    private SampleFamily createMeterSamples(EntityDescription entityDescription) {
+        Map<MeterEntity, Sample[]> meterSamples = new HashMap<>();
+        Arrays.stream(samples)
+              .collect(groupingBy(it -> InternalOps.getLabels(entityDescription.getLabelKeys(), it),
+                                  mapping(identity(), toList())
+              ))
+              .forEach((labels, samples) -> {
+                  MeterEntity meterEntity = InternalOps.buildMeterEntity(samples, entityDescription);
+                  meterSamples.put(
+                      meterEntity, InternalOps.left(samples, entityDescription.getLabelKeys()));
+              });
+
+        this.context.setMeterSamples(meterSamples);
+        return SampleFamily.build(this.context, samples);
+    }
+
+    private SampleFamily match(String[] labels, Function2<String, String, Boolean> op) {
+        Preconditions.checkArgument(labels.length % 2 == 0);
+        Map<String, String> ll = new HashMap<>(labels.length / 2);
+        for (int i = 0; i < labels.length; i += 2) {
+            ll.put(labels[i], labels[i + 1]);
+        }
+        Sample[] ss = Arrays.stream(samples)
+                            .filter(sample -> ll.entrySet()
+                                                .stream()
+                                                .allMatch(
+                                                    entry -> op.apply(sample.labels.getOrDefault(entry.getKey(), ""),
+                                                                      entry.getValue()
+                                                    )))
+                            .toArray(Sample[]::new);
+        return ss.length > 0 ? SampleFamily.build(this.context, ss) : EMPTY;
+    }
+
+    private SampleFamily valueMatch(CompType compType,
+                                    double compValue,
+                                    Function3<CompType, Double, Double, Boolean> op) {
+        Sample[] ss = Arrays.stream(samples)
+                            .filter(sample -> op.apply(compType, sample.value, compValue)).toArray(Sample[]::new);
+        return ss.length > 0 ? SampleFamily.build(this.context, ss) : EMPTY;
+    }
+
+    public SampleFamily newValue(Function<Double, Double> transform) {
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        Sample[] ss = new Sample[samples.length];
+        for (int i = 0; i < ss.length; i++) {
+            ss[i] = samples[i].newValue(transform);
+        }
+        return SampleFamily.build(this.context, ss);
+    }
+
+    private SampleFamily newValue(SampleFamily another, Function2<Double, Double, Double> transform) {
+        Sample[] ss = Arrays.stream(samples)
+                            .flatMap(cs -> io.vavr.collection.Stream.of(another.samples)
+                                                                    .find(as -> cs.labels.equals(as.labels))
+                                                                    .map(as -> cs.toBuilder()
+                                                                                 .value(transform.apply(cs.value,
+                                                                                                        as.value
+                                                                                 )))
+                                                                    .map(Sample.SampleBuilder::build)
+                                                                    .toJavaStream())
+                            .toArray(Sample[]::new);
+        return ss.length > 0 ? SampleFamily.build(this.context, ss) : EMPTY;
+    }
+
+    public SampleFamily downsampling(final DownsamplingType type) {
+        ExpressionParsingContext.get().ifPresent(it -> it.downsampling = type);
+        return this;
+    }
+
+    /**
+     * Decorate the service meter entity using a Java functional interface.
+     * Replaces upstream decorate(Closure) — no Groovy Closure needed.
+     */
+    public SampleFamily decorate(DecorateFunction fn) {
+        ExpressionParsingContext.get().ifPresent(ctx -> {
+            if (ctx.getScopeType() != ScopeType.SERVICE) {
+                throw new IllegalStateException("decorate() should be invoked after service()");
+            }
+            if (ctx.isHistogram()) {
+                throw new IllegalStateException("decorate() not supported for histogram metrics");
+            }
+        });
+        if (this == EMPTY) {
+            return EMPTY;
+        }
+        this.context.getMeterSamples().keySet().forEach(meterEntity -> {
+            if (meterEntity.getScopeType().equals(ScopeType.SERVICE)) {
+                fn.accept(meterEntity);
+            }
+        });
+        return this;
+    }
+
+    @ToString
+    @EqualsAndHashCode(exclude = "metricName")
+    @Getter
+    @Setter
+    @Builder
+    public static class RunningContext {
+
+        static RunningContext EMPTY = instance();
+
+        static RunningContext instance() {
+            return RunningContext.builder()
+                                 .defaultHistogramBucketUnit(TimeUnit.SECONDS)
+                                 .build();
+        }
+
+        private String metricName;
+
+        @Builder.Default
+        private Map<MeterEntity, Sample[]> meterSamples = new HashMap<>();
+
+        private TimeUnit defaultHistogramBucketUnit;
+    }
+
+    private static class InternalOps {
+
+        private static Sample[] left(List<Sample> samples, List<String> labelKeys) {
+            return samples.stream().map(s -> {
+                ImmutableMap<String, String> ll = ImmutableMap.<String, String>builder()
+                                                              .putAll(Maps.filterKeys(s.labels,
+                                                                                      key -> !labelKeys.contains(key)
+                                                              ))
+                                                              .build();
+                return s.toBuilder().labels(ll).build();
+            }).toArray(Sample[]::new);
+        }
+
+        private static String dim(List<Sample> samples, List<String> labelKeys, String delimiter) {
+            String name = labelKeys.stream()
+                                   .map(k -> samples.get(0).labels.getOrDefault(k, ""))
+                                   .filter(v -> !StringUtil.isEmpty(v))
+                                   .collect(Collectors.joining(StringUtil.isEmpty(delimiter) ? Const.POINT : delimiter));
+            return name;
+        }
+
+        private static MeterEntity buildMeterEntity(List<Sample> samples,
+                                                    EntityDescription entityDescription) {
+            switch (entityDescription.getScopeType()) {
+                case SERVICE:
+                    ServiceEntityDescription serviceEntityDescription = (ServiceEntityDescription) entityDescription;
+                    return MeterEntity.newService(
+                        InternalOps.dim(samples, serviceEntityDescription.getServiceKeys(), serviceEntityDescription.getDelimiter()),
+                        serviceEntityDescription.getLayer()
+                    );
+                case SERVICE_INSTANCE:
+                    InstanceEntityDescription instanceEntityDescription = (InstanceEntityDescription) entityDescription;
+                    Map<String, String> properties = null;
+                    if (instanceEntityDescription.getPropertiesExtractor() != null) {
+                        properties = instanceEntityDescription.getPropertiesExtractor().apply(samples.get(0).labels);
+                    }
+                    return MeterEntity.newServiceInstance(
+                        InternalOps.dim(samples, instanceEntityDescription.getServiceKeys(), instanceEntityDescription.getServiceDelimiter()),
+                        InternalOps.dim(samples, instanceEntityDescription.getInstanceKeys(), instanceEntityDescription.getInstanceDelimiter()),
+                        instanceEntityDescription.getLayer(),
+                        properties
+                    );
+                case ENDPOINT:
+                    EndpointEntityDescription endpointEntityDescription = (EndpointEntityDescription) entityDescription;
+                    return MeterEntity.newEndpoint(
+                        InternalOps.dim(samples, endpointEntityDescription.getServiceKeys(), endpointEntityDescription.getDelimiter()),
+                        InternalOps.dim(samples, endpointEntityDescription.getEndpointKeys(), endpointEntityDescription.getDelimiter()),
+                        endpointEntityDescription.getLayer()
+                    );
+                case PROCESS:
+                    final ProcessEntityDescription processEntityDescription = (ProcessEntityDescription) entityDescription;
+                    return MeterEntity.newProcess(
+                        InternalOps.dim(samples, processEntityDescription.getServiceKeys(), processEntityDescription.getDelimiter()),
+                        InternalOps.dim(samples, processEntityDescription.getServiceInstanceKeys(), processEntityDescription.getDelimiter()),
+                        InternalOps.dim(samples, processEntityDescription.getProcessKeys(), processEntityDescription.getDelimiter()),
+                        InternalOps.dim(samples, List.of(processEntityDescription.getLayerKey()), processEntityDescription.getDelimiter())
+                    );
+                case SERVICE_RELATION:
+                    ServiceRelationEntityDescription serviceRelationEntityDescription = (ServiceRelationEntityDescription) entityDescription;
+                    final String serviceRelationComponentValue = InternalOps.dim(samples,
+                        Collections.singletonList(serviceRelationEntityDescription.getComponentIdKey()), serviceRelationEntityDescription.getDelimiter());
+                    int serviceRelationComponentId = StringUtil.isNotEmpty(serviceRelationComponentValue) ? Integer.parseInt(serviceRelationComponentValue) : 0;
+                    return MeterEntity.newServiceRelation(
+                        InternalOps.dim(samples, serviceRelationEntityDescription.getSourceServiceKeys(), serviceRelationEntityDescription.getDelimiter()),
+                        InternalOps.dim(samples, serviceRelationEntityDescription.getDestServiceKeys(), serviceRelationEntityDescription.getDelimiter()),
+                        serviceRelationEntityDescription.getDetectPoint(), serviceRelationEntityDescription.getLayer(), serviceRelationComponentId
+                    );
+                case PROCESS_RELATION:
+                    final ProcessRelationEntityDescription processRelationEntityDescription = (ProcessRelationEntityDescription) entityDescription;
+                    final String detectPointValue = InternalOps.dim(samples, Collections.singletonList(processRelationEntityDescription.getDetectPointKey()), processRelationEntityDescription.getDelimiter());
+                    DetectPoint point = StringUtils.equalsAnyIgnoreCase(detectPointValue, "server") ? DetectPoint.SERVER : DetectPoint.CLIENT;
+                    final String componentValue = InternalOps.dim(samples, Collections.singletonList(processRelationEntityDescription.getComponentKey()), processRelationEntityDescription.getDelimiter());
+                    final int componentId = StringUtil.isNotEmpty(componentValue) ? Integer.parseInt(componentValue) : 0;
+                    return MeterEntity.newProcessRelation(
+                        InternalOps.dim(samples, processRelationEntityDescription.getServiceKeys(), processRelationEntityDescription.getDelimiter()),
+                        InternalOps.dim(samples, processRelationEntityDescription.getInstanceKeys(), processRelationEntityDescription.getDelimiter()),
+                        InternalOps.dim(samples, Collections.singletonList(processRelationEntityDescription.getSourceProcessIdKey()), processRelationEntityDescription.getDelimiter()),
+                        InternalOps.dim(samples, Collections.singletonList(processRelationEntityDescription.getDestProcessIdKey()), processRelationEntityDescription.getDelimiter()),
+                        componentId,
+                        point
+                    );
+                default:
+                    throw new UnexpectedException(
+                        "Unexpected scope type of entityDescription " + entityDescription);
+            }
+        }
+
+        private static Sample newSample(String name,
+                                        ImmutableMap<String, String> labels,
+                                        long timestamp,
+                                        double newValue) {
+            return Sample.builder()
+                         .value(newValue)
+                         .labels(labels)
+                         .timestamp(timestamp)
+                         .name(name)
+                         .build();
+        }
+
+        private static boolean stringComp(String a, String b) {
+            if (Strings.isNullOrEmpty(a) && Strings.isNullOrEmpty(b)) {
+                return true;
+            }
+            if (Strings.isNullOrEmpty(a)) {
+                return false;
+            }
+            return a.equals(b);
+        }
+
+        private static boolean doubleComp(CompType compType, double a, double b) {
+            int result = Double.compare(a, b);
+            switch (compType) {
+                case EQUAL:
+                    return result == 0;
+                case NOT_EQUAL:
+                    return result != 0;
+                case GREATER:
+                    return result == 1;
+                case GREATER_EQUAL:
+                    return result == 0 || result == 1;
+                case LESS:
+                    return result == -1;
+                case LESS_EQUAL:
+                    return result == 0 || result == -1;
+            }
+
+            return false;
+        }
+
+        private static ImmutableMap<String, String> getLabels(final List<String> labelKeys, final Sample sample) {
+            return labelKeys.stream()
+                            .collect(toImmutableMap(
+                                Function.identity(),
+                                labelKey -> sample.labels.getOrDefault(labelKey, "")
+                            ));
+        }
+
+        private static ImmutableMap<String, String> groupByExcludedLabel(final String excludedLabelKey, final Sample sample) {
+            return sample
+                    .labels
+                    .entrySet()
+                    .stream()
+                    .filter(v -> !v.getKey().equals(excludedLabelKey))
+                    .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
+        }
+    }
+
+    private enum CompType {
+        EQUAL, NOT_EQUAL, LESS, LESS_EQUAL, GREATER, GREATER_EQUAL
+    }
+}
diff --git a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamilyFunctions.java b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamilyFunctions.java
new file mode 100644
index 0000000..41bc728
--- /dev/null
+++ b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamilyFunctions.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.meter.analyzer.dsl;
+
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import org.apache.skywalking.oap.server.core.analysis.meter.MeterEntity;
+
+/**
+ * Java functional interfaces replacing Groovy Closure parameters in SampleFamily methods.
+ * These are used by the generated MAL expression classes (transpiled from Groovy DSL).
+ */
+public final class SampleFamilyFunctions {
+
+    private SampleFamilyFunctions() {
+    }
+
+    /**
+     * Tag transformation function: receives mutable tag map, modifies it, returns modified map.
+     * Replaces Closure&lt;?&gt; in SampleFamily.tag().
+     */
+    @FunctionalInterface
+    public interface TagFunction extends Function<Map<String, String>, Map<String, String>> {
+    }
+
+    /**
+     * Sample filter predicate: receives tag map, returns true to keep the sample.
+     * Replaces Closure&lt;Boolean&gt; in SampleFamily.filter().
+     */
+    @FunctionalInterface
+    public interface SampleFilter extends Predicate<Map<String, String>> {
+    }
+
+    /**
+     * ForEach function: receives an array element and mutable label map.
+     * Replaces Closure&lt;Void&gt; in SampleFamily.forEach().
+     */
+    @FunctionalInterface
+    public interface ForEachFunction extends BiConsumer<String, Map<String, String>> {
+    }
+
+    /**
+     * Decorate function: receives a MeterEntity to modify.
+     * Replaces Closure&lt;Void&gt; in SampleFamily.decorate().
+     */
+    @FunctionalInterface
+    public interface DecorateFunction extends Consumer<MeterEntity> {
+    }
+
+    /**
+     * Properties extractor: receives label map, returns properties map.
+     * Replaces Closure&lt;Map&gt; in SampleFamily.instance() overload.
+     */
+    @FunctionalInterface
+    public interface PropertiesExtractor extends Function<Map<String, String>, Map<String, String>> {
+    }
+}