# MVEL 3 transpiles expressions into Java source and compiles them with javac

> MVEL3 is a rewrite of the MVEL expression language that turns expressions into Java source, compiles them in memory with javac and runs the resulting bytecode, rather than interpreting them at runtime as MVEL2 does. It fits a Java team that wants an embeddable expression language, and it does not fit a case where expressions come from untrusted users, which the README rules out explicitly.

**mvel/mvel** — MVEL (MVFLEX Expression Language)

- Repository: https://github.com/mvel/mvel
- Website: http://mvel.documentnode.com/
- Stars: 1,206 · Forks: 323
- Language: Java
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/mvel-mvel

## The rewrite changes the execution model, not just the syntax

MVEL is the MVFLEX Expression Language, described as a hybrid dynamic and statically typed, embeddable expression language and runtime for the Java Platform.

The headline change in MVEL3 is mechanical and it changes everything downstream. MVEL3 is a complete rewrite of MVEL, and instead of interpreting expressions at runtime, as MVEL2 does, MVEL3 transpiles MVEL expressions into Java source code, compiles them in-memory via javac, and executes the resulting bytecode.

That is a different performance and semantics model. An interpreter runs a tree of operations for every evaluation, while the transpiled version runs JVM bytecode the JIT can optimise. The cost is a compilation step per expression and the loss of dynamic behaviour that only an interpreter offers, such as deciding a method call at runtime based on the actual runtime type of an argument.

The README carries two notices, and both belong in the first paragraph of any evaluation. The alpha notice says this is an alpha release and APIs may change in future releases. The security notice says MVEL expressions are functionally equivalent to Java code and can perform any operation the JVM allows, including file system access and network calls, and instructs you to only compile and execute expressions from trusted sources, pointing at SECURITY.md for details.

The last push was on 2026-10-14, and the repository has no GitHub releases, so the version you get is whatever the build produces.

## Three context types, three different variable models

MVEL3 provides a fluent builder API with three context types, and the choice determines how variables are bound.

The Map context passes variables via Map<String, Object> and extracts them by name. The example declares a types map with Type.type(int.class) entries for x and y, builds an Evaluator with the expression x + y, and evaluates against a map of values, returning 8. The interesting part is the type declaration, which is what makes the expression statically typed even though the values arrive dynamically.

The POJO context extracts variables from an object via getter methods, and the example declares age as an int on a Person class and evaluates age > 20, returning true. The classManager is supplied explicitly, which is how the compiler knows which types are in scope.

The List context extracts variables from a List by positional index, with the expression a + b and a declaration array binding a and b to positions. That is the shape you would use for a rule engine evaluating over a tuple.

The three contexts are the same expression language with different binding strategies, and the builder API is typed over the context type. An Evaluator<Map<String, Object>, Void, Integer> and an Evaluator<Person, Void, Boolean> differ in their first and third type parameters, so the compiler will stop you from evaluating a Map expression against a POJO.

There is also a shorter form for the List case, compileListExpression, which takes the expression, the return type, the imports and the declarations directly, without the builder.

## Expressions return a value, blocks assign and write back

The distinction between an expression and a block is the second piece of the API, and it comes with a consequence that is easy to miss.

An expression is a single expression that returns its value directly, so .expression("x + y"). A block is multiple statements, where you use return to specify the return value, so .block("a = 4; b = 5; int c = 6; return a + b + c;").

The consequence is stated in the README: variables assigned in a block are written back to the context, with Map entries updated and POJO setters called.

That is a real semantic difference from most expression languages. In a typical scripting engine, an expression is pure and assigning to a variable in it either fails or is scoped locally. Here, a block is a statement sequence that can mutate the context it was evaluated against. For a rules engine that wants to accumulate state across evaluations, that is the feature. For code that assumes evaluation is side-effect free, it is a trap, and it means the same compiled evaluator is not safe to run concurrently against a shared context.

The two forms also have different builders in the examples, .expression and .block, so the choice is made at compile time rather than at evaluation time.

## Types and imports are declared up front, which is the price of compilation

Because MVEL3 compiles to Java source, the things the compiler needs must be given to it before evaluation.

Generics are specified with Type.type(), and the examples show both a plain parameterised type and one referencing a project class, with types.put("names", Type.type(List.class, "<String>")) and a second entry binding a list of Person using the canonical name of that class.

Imports are a Set<String> of fully qualified class names for types used in expressions, and the example adds java.util.List and the canonical name of Person. The builder method is .imports(Collections.emptySet()), so an expression that references no external type needs an empty set rather than nothing, which makes the set of names in scope explicit at the call site.

The POJO example also passes a ClassManager, .classManager(new ClassManager()), which is the mechanism for telling the compiler what types exist in the context. In the Map example that is replaced by the explicit types map, since there the type of each variable is given by name rather than by a class.

The practical effect is that an MVEL3 expression is not self-describing in the way an MVEL2 expression was. Anything the expression touches has to be declared somewhere, which is more work up front and considerably better diagnostics later.

## Building MVEL3 needs a forked parser installed first

The build instructions are unusually specific because a forked parser must be installed first.

The prerequisites are JDK 17+ and Maven 3.8.7+. MVEL3 requires a javaparser-mvel fork with MVEL-specific AST nodes, and it must be built first:

```bash
git clone https://github.com/mvel/javaparser-mvel.git
cd javaparser-mvel
mvn clean install
```

Then MVEL3 itself:

```bash
git clone https://github.com/mvel/mvel.git
cd mvel
mvn clean install
```

The two-step build is a real friction point. The parser fork has to be installed into the local Maven repository before MVEL3 will resolve, so a CI setup has to build it in the right order, and anyone who skips that step gets a dependency resolution error rather than a clear message about the fork.

The repository layout is a conventional Maven project: pom.xml, src/, docs/, a CHANGELOG.md, a SECURITY.md, a LICENSE.txt, and.github/ for the workflows. The two branches matter more than the layout. This is the MVEL version 3.x codebase, developed in the main branch, while MVEL2 is maintained separately in the mvel2 branch, and the README asks that contributions to MVEL2 go to that branch as a pull request.

## Where MVEL is the wrong tool

The first wrong-tool case is untrusted expressions, and the project says so itself. MVEL expressions are functionally equivalent to Java code and can perform any operation the JVM allows, including file system access and network calls. There is no sandbox, no capability list and no restricted runtime in the design described here. Any product feature that lets a user or a tenant type an expression is a remote code execution surface, and the README's instruction is to only compile and execute expressions from trusted sources.

The second is a dynamic requirement that compilation cannot satisfy. Because MVEL3 compiles to Java source, decisions that an interpreter makes at runtime, such as dispatching on the runtime type of an argument or handling a property that does not exist until it is read, are either constrained or lost. MVEL2, which interprets, supports those, and for some rule engines that flexibility is the reason to use MVEL in the first place. The rewrite moves the library toward a statically typed model, which is faster and more diagnosable but a different language in practice.

The third is the alpha status. APIs may change in future releases, and this is the main branch of a rewrite rather than a stable line. MVEL2 is the maintained alternative for anyone who needs stability, and it lives in a separate branch with its own pull request target.

The fourth is the build. A fork of javaparser-mvel that has to be cloned and installed locally before MVEL3 will build is a supply chain step that a team has to own, and it is the practical friction that would make a team look at a plain scripting engine bundled with the JVM, or at a rule engine with a restricted expression language, before choosing MVEL.

## Conclusion

MVEL3 fits a Java team that wants an embeddable, rule-like expression language for configuration, rules or scripting inside a running JVM, and it does not fit a case where the expressions come from users you do not trust, because the README states MVEL expressions are functionally equivalent to Java code and can perform any operation the JVM allows. Before adopting it, read SECURITY.md, and note that this is an alpha release whose APIs may change, with MVEL2 maintained separately on the mvel2 branch.

## FAQ

### What is MVEL3 and how does it differ from MVEL2?

MVEL3 is a complete rewrite of the MVEL expression language. Instead of interpreting expressions at runtime as MVEL2 does, MVEL3 transpiles them into Java source code, compiles them in memory via javac and executes the resulting bytecode. MVEL2 is maintained separately in the mvel2 branch.

### What context types does the MVEL3 API support?

Three. Map passes variables as Map<String, Object> and extracts them by name, POJO extracts them via getter methods, and List extracts them by positional index. The Evaluator is typed over the context, so a Map evaluator and a POJO evaluator are different types.

### What is the difference between a MVEL3 expression and a block?

An expression is a single expression that returns its value directly, while a block is multiple statements using return to specify the return value. Variables assigned in a block are written back to the context, with Map entries updated and POJO setters called.

### Can MVEL expressions be run from untrusted sources?

No. The README's security notice states that MVEL expressions are functionally equivalent to Java code and can perform any operation the JVM allows, including file system access and network calls, and instructs you to only compile and execute expressions from trusted sources. See SECURITY.md for details.

## Sources

- [Issues](https://github.com/mvel/mvel/issues)
- [License: Apache-2.0](https://github.com/mvel/mvel/blob/main/LICENSE)
- [mvel/mvel on GitHub](https://github.com/mvel/mvel)
- [Project website](http://mvel.documentnode.com/)
- [README](https://github.com/mvel/mvel/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mvel-mvel
