Backend Development 18 min read

AviatorScript: Features, Usage, and Spring Boot Integration for Expression Evaluation and Validation

This article introduces AviatorScript, a lightweight high‑performance JVM‑based scripting language, outlines its core features and limitations, demonstrates how to add it to a Maven project, shows expression evaluation, variable handling, custom functions, script files, and provides a practical Spring Boot AOP validation example with full code snippets.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
AviatorScript: Features, Usage, and Spring Boot Integration for Expression Evaluation and Validation

Aviator originally was a lightweight, high‑performance expression engine for the JVM; from version 5.0.0 it evolved into AviatorScript, a full‑featured script language that runs on the JVM (including Android).

Key features include support for basic types (numbers, strings, regex, booleans), first‑class functions with closures, bigint/decimal types with operator overloading ( +-*/ ), complete script syntax (multiline data, conditionals, loops, lexical scope, exception handling), sequence abstraction for functional collection processing, a lightweight module system, easy Java method invocation, sandbox options, and ASM‑based compilation that translates scripts directly to JVM bytecode for high performance.

Typical scenarios are rule engines, formula calculations, dynamic script control, and ELT‑style collection processing.

Limitations : no if‑else , do‑while , or assignment statements; only logical, arithmetic, ternary, and regex expressions are supported, and octal literals are not allowed (only decimal and hexadecimal).

Basic usage : add the Maven dependency.

<dependency>
    <groupId>com.googlecode.aviator</groupId>
    <artifactId>aviator</artifactId>
    <version>5.3.3</version>
</dependency>

Then evaluate an expression directly:

// returns 16
Long r = (Long) AviatorEvaluator.execute("2 * (3 + 5)");

For better performance, compile the expression first and reuse it:

Expression expression = AviatorEvaluator.compile("2 * (3 + 5)");
Long r = (Long) expression.execute();

Aviator supports numbers, strings, booleans, and treats numeric values as long or double . Example of various operators:

// string concatenation
String r = (String) AviatorEvaluator.execute("'hello' + ' world'");
// logical expression
Boolean r = (Boolean) AviatorEvaluator.execute("100 > 80 && 30 < 40");
// ternary expression
Long r = (Long) AviatorEvaluator.execute("100 > 80 ? 30 : 40");
// regex match
Boolean r = (Boolean) AviatorEvaluator.execute("'hello' =~ /[\\w]+/");

Expression variables can be passed as parameters:

Long a = 12L;
Boolean r = (Boolean) AviatorEvaluator.exec("a > 10", a);

List
a = new ArrayList<>();
a.add(12L);
a.add(20L);
Boolean r = (Boolean) AviatorEvaluator.exec("a[0] > 10", a);

public class Person {
    private String name;
    private Integer age;
}
Person p = new Person("movee", 25);
Boolean r = (Boolean) AviatorEvaluator.exec("p.age > 10", p);

Map
env = new HashMap<>();
env.put("person", new Person("movee", 25));
env.put("a", 20L);
Object result = AviatorEvaluator.execute("person.name", env);

Aviator can also extract values from JSON strings:

String jsonStr = "{\"a\":{\"b\":[{\"x\":3},{\"x\":4}]}}";
JSONObject jsonObj = new JSONObject(jsonStr);
Object value = AviatorEvaluator.execute("a.b[0]['x']", jsonObj.toMap()); // returns 3

Built‑in functions such as math.round , string.length , and seq.list are available, and custom functions can be created by extending AbstractFunction :

public class AddFunction extends AbstractFunction {
    @Override
    public AviatorObject call(Map
env, AviatorObject arg1, AviatorObject arg2) {
        long num1 = FunctionUtils.getNumberValue(arg1, env).longValue();
        long num2 = FunctionUtils.getNumberValue(arg2, env).longValue();
        return AviatorLong.valueOf(num1 + num2);
    }
    @Override
    public String getName() { return "add"; }
}

AviatorEvaluator.addFunction(new AddFunction());
Long sum = (Long) AviatorEvaluator.getInstance().execute("add(3,4)");

AviatorScript files usually have the .av suffix. Example hello.av :

if (a > 10) {
    return 10;
} else {
    return a;
}

Execute the script with parameters:

Map
env = new HashMap<>();
env.put("a", 30);
Expression exp = AviatorEvaluator.getInstance().compileScript("./hello.av", true);
Object result = exp.execute(env);

Practical example: using Aviator + AOP for parameter validation in Spring Boot

pom.xml snippet (relevant dependencies):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>com.googlecode.aviator</groupId>
    <artifactId>aviator</artifactId>
    <version>3.3.0</version>
</dependency>

Controller method annotated with multiple @Check annotations:

@RestController
public class HelloWorldController {
    @GetMapping("/simple")
    @Check(ex = "name != null", msg = "Name cannot be empty")
    @Check(ex = "age != null", msg = "Age cannot be empty")
    @Check(ex = "age > 18", msg = "Age must be over 18 years old")
    @Check(ex = "phone != null", msg = "phone cannot be empty")
    @Check(ex = "phone =~ /^(1)[0-9]{10}$/", msg = "The phone number format is incorrect")
    @Check(ex = "string.startsWith(phone,\"1\")", msg = "The phone number must start with 1")
    @Check(ex = "idCard != null", msg = "ID number cannot be empty")
    @Check(ex = "idCard =~ /^[1-9]\\d{5}[1-9]\\d{3}((0[1-9])||(1[0-2]))((0[1-9])||(1\\d)||(2\\d)||(3[0-1]))\\d{3}([0-9]||X)$/", msg = "ID number format is incorrect")
    @Check(ex = "gender == 1", msg = "sex")
    @Check(ex = "date =~ /^[1-9][0-9]{3}-((0)[1-9]|(1)[0-2])-((0)[1-9]|[1,2][0-9]|(3)[0,1])$/", msg = "Wrong date format")
    @Check(ex = "date > '2019-12-20 00:00:00:00'", msg = "The date must be greater than 2019-12-20")
    public HttpResult simple(String name, Integer age, String phone, String idCard, String date) {
        System.out.println("name = " + name);
        System.out.println("age = " + age);
        System.out.println("phone = " + phone);
        System.out.println("idCard = " + idCard);
        System.out.println("date = " + date);
        return HttpResult.success();
    }
}

Definition of @Check annotation and its container:

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(CheckContainer.class)
public @interface Check {
    String ex() default "";
    String msg() default "";
}

public @interface CheckContainer {
    Check[] value();
}

AOP configuration that intercepts methods annotated with @Check or @CheckContainer , builds a parameter map, evaluates each expression with Aviator, and throws UserFriendlyException on failure:

@Aspect
@Configuration
public class AopConfig {
    @Pointcut("@annotation(com.et.annotation.CheckContainer) || @annotation(com.et.annotation.Check)")
    public void pointcut() {}

    @Before("pointcut()")
    public Object before(JoinPoint point) {
        Object[] args = point.getArgs();
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        CheckContainer container = method.getDeclaredAnnotation(CheckContainer.class);
        List
checks = new ArrayList<>();
        if (container != null) {
            checks.addAll(Arrays.asList(container.value()));
        } else {
            checks.add(method.getDeclaredAnnotation(Check.class));
        }
        for (Check check : checks) {
            String ex = check.ex().replaceAll("null", "nil");
            String msg = StringUtils.isEmpty(check.msg()) ? "server exception..." : check.msg();
            Map
map = new HashMap<>(16);
            for (int i = 0; i < paramNames.length && i < args.length; i++) {
                map.put(paramNames[i], args[i]);
            }
            Boolean result = (Boolean) AviatorEvaluator.execute(ex, map);
            if (!result) {
                throw new UserFriendlyException(msg);
            }
        }
        return null;
    }
}

Global exception handler that formats validation and server errors into a unified HttpResult response:

@Configuration
@ControllerAdvice
public class DefaultGlobalExceptionHandler extends ResponseEntityExceptionHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultGlobalExceptionHandler.class);

    @Override
    protected ResponseEntity
handleExceptionInternal(Exception ex, @Nullable Object body,
            HttpHeaders headers, HttpStatus status, WebRequest request) {
        HttpResult httpResult = HttpResult.failure(status.is5xxServerError() ?
                ErrorCode.serverError.getDesc() : ErrorCode.paramError.getDesc());
        LOGGER.error("handleException, ex caught, contextPath={}, httpResult={}, ex.msg={}",
                request.getContextPath(), JSON.toJSONString(httpResult), ex.getMessage());
        return super.handleExceptionInternal(ex, httpResult, headers, status, request);
    }

    @ExceptionHandler(Exception.class)
    protected ResponseEntity handleException(HttpServletRequest request, Exception ex) {
        boolean is5xxServerError;
        HttpStatus httpStatus;
        HttpResult httpResult;
        if (ex instanceof UserFriendlyException) {
            UserFriendlyException ue = (UserFriendlyException) ex;
            is5xxServerError = ue.getHttpStatusCode() >= 500;
            httpStatus = HttpStatus.valueOf(ue.getHttpStatusCode());
            httpResult = HttpResult.failure(ue.getErrorCode(), ue.getMessage());
        } else if (ex instanceof IllegalArgumentException) {
            httpStatus = HttpStatus.OK;
            is5xxServerError = false;
            httpResult = HttpResult.failure("Parameter verification error or data abnormality!");
        } else {
            httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
            is5xxServerError = true;
            httpResult = HttpResult.failure(ErrorCode.serverError.getDesc());
        }
        if (is5xxServerError) {
            LOGGER.error("handleException, ex caught, uri={}, httpResult={}", request.getRequestURI(),
                    JSON.toJSONString(httpResult), ex);
        } else {
            LOGGER.error("handleException, ex caught, uri={}, httpResult={}, ex.msg={}",
                    request.getRequestURI(), JSON.toJSONString(httpResult), ex.getMessage());
        }
        return new ResponseEntity<>(httpResult, httpStatus);
    }
}

Finally, the article includes a short promotional paragraph for a new booklet covering 40 common backend pain points and a set of recommended reading links.

JavaAOPValidationSpring Bootaviatorexpression-engine
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.