← Back to list

Extending MVEL with Custom Functions: The Right Way to Use 'in' for Full Containment

How to Handle Multi-Value “In” Logic in MVEL When Native Support Falls Short

NGU in Level Up Coding · 2025-06-25 17:13 · 5 claps · 2.0 min read paywalled
#java #mvel #programming #coding #rule-engine
Open on Medium ↗
Wiki topics: 💻 · Programming

Extending MVEL with Custom Functions: The Right Way to Use 'in' for Full Containment

How to Handle Multi-Value “In” Logic in MVEL When Native Support Falls Short

Problem Overview

MVEL (MVFLEX Expression Language) is widely used for dynamic expression evaluation in Java-based rule engines. However, it does not support the in keyword out-of-the-box the way you might expect:

rating in ['Excellent', 'Good', 'Normal'] // ❌ Will throw error

While MVEL does support .contains(...), it's strictly for string matching or manual collections — and has no concept of multi-value containment like SQL’s IN.

Goal

We want to support expressions like:

inList(overallRating, ['Excellent', 'Good', 'Normal'])

Where:

  • rating can be a single value like "Good" or a list of values like ["Good", "Normal"]
  • The match is considered true Only if all values are contained in the provided list

Step 1 — Define the Custom Function

public class CustomFunctions {
    /**
     * Check if the value is fully contained in the list.
     * Supports both single values and collections.
     */
    public static boolean inList(Object value, Collection<?> list) {
        if (value == null || list == null) return false;

        if (value instanceof Collection<?>) {
            return list.containsAll((Collection<?>) value); // ✅ must match all
        }        
        return list.contains(value);
    }
}

Step 2 — Register the Function with MVEL

import org.mvel2.MVEL;
import org.mvel2.ParserContext;
import java.io.Serializable;
import java.util.*;

public class MvelExpressionRunner {

  public static boolean runExpression(String expression, Map<String, Object> context) {
        ParserContext parserContext = new ParserContext();

        try {
            parserContext.addImport("inList", CustomFunctions.class.getMethod(
                    "inList", Object.class, Collection.class));
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Failed to register custom method", e);
        }
        Serializable compiled = MVEL.compileExpression(expression, parserContext);
        return (Boolean) MVEL.executeExpression(compiled, context);
    }
}

Step 3 — Full JUnit Tests

import org.junit.jupiter.api.Test;
import java.util.*;
import static org.junit.jupiter.api.Assertions.*;

class MvelCustomFunctionTest {

    @Test
    void testSingleValueMatch() {
        Map<String, Object> ctx = Map.of(
                "rating", "B",
                "validRatings", List.of("A", "B", "C")
        );
        String expr = "inList(rating, validRatings)";
        assertTrue(MvelExpressionRunner.runExpression(expr, ctx));
    }

    @Test
    void testMultiValueExactMatch() {
        Map<String, Object> ctx = Map.of(
                "rating", List.of("B", "C"),
                "validRatings", List.of("A", "B", "C")
        );
        String expr = "inList(rating, validRatings)";
        assertTrue(MvelExpressionRunner.runExpression(expr, ctx));
    }

    @Test
    void testMultiValuePartialFail() {
        Map<String, Object> ctx = Map.of(
                "rating", List.of("B", "X"),
                "validRatings", List.of("A", "B", "C")
        );
        String expr = "inList(rating, validRatings)";
        assertFalse(MvelExpressionRunner.runExpression(expr, ctx)); // ✅ fails now
    }

    @Test
    void testFullLogicalRule() {
        Map<String, Object> ctx = new HashMap<>();
        ctx.put("age", 90);
        ctx.put("status", "On");
        ctx.put("rating", List.of("Excellent", "Good"));
        ctx.put("validRatings", List.of("Excellent", "Good", "Normal"));

        String expr = "age < 94 && status == 'On' && inList(rating, validRatings)";
        assertTrue(MvelExpressionRunner.runExpression(expr, ctx));
    }
}

Summary

🚀 Final Thoughts

With this setup, you can:

  • Write flexible rules in MVEL using expressive DSL
  • Handle advanced logic (multi-value match, containment) without post-processing
  • Cleanly separate logic and code via a rule engine

That’s it.

Thanks for reading! If you like it or feel it helped, pls click Applaud. Happy coding.

See you next time :)


메타데이터
post_id
35d51d01b4c8
slug
extending-mvel-with-custom-functions-the-right-way-to-use-in-for-full-containment-35d51d01b4c8
url
https://levelup.gitconnected.com/extending-mvel-with-custom-functions-the-right-way-to-use-in-for-full-containment-35d51d01b4c8
canonical_url
https://levelup.gitconnected.com/extending-mvel-with-custom-functions-the-right-way-to-use-in-for-full-containment-35d51d01b4c8
author_url
https://medium.com/@bayern01kahn
status
ok
fetched_at
2026-07-19 07:01:35