Behavioral Design Pattern: Interpreter
design-patterns
This article is part of a series exploring design patterns using the Java programming language. The goal of this series is to help readers develop a solid understanding of design patterns while also sharing real-world examples from actual codebases that make use of these patterns.
In this article, we’ll be discussing the Interpreter pattern.
Interpreter Pattern
The Interpreter pattern is a pattern that you’d typically use to decipher any context based on various rules. Think of it like an Abstract Syntax Tree (AST), where each node is either a branch node or a terminal node to evaluate expressions.
More theoretically, it is a design pattern which specifies how to evaluate a sentence in a language [1].
If the business logic requires us to break down the context to make a decision, this is where the Interpreter pattern comes into play.
Take, for example, any query languages created on top of products to retrieve data from systems. I can think of JIRA as a software which uses Jira Query Language* or Google using advanced search with the identifiers* to enhance results.
assignee = currentUser() AND priority = High
In the above snippet, we are fetching the data where the current user is an assignee and the priority of the ticket is High.
Now, let’s visualize this snippet itself and break it down into a tree that can help retrieve that data.

AST representation of a JQL query
If we take a look at each node, it’s either a branch or decision node, or it’s a terminal node with the results. Each node takes us through a series of business rules which in the end get converted into the exact data.
Components of Interpreter Pattern
The Interpreter pattern is composed of the following components:
- Context: The data required to interpret the request from the client. For example, in the case of JQL, it’s a String; in other cases, it could be a semantic structure like JSON.
- Expression: The expression object which takes in the context and evaluates the context data. The expression can be either non-terminal (i.e., a branch or compound expression) or it can be a terminal expression [2].
- Rules: The rules that lead to final results based on the context. These are defined by the users.

UML Class Diagram of Interpreter Pattern
All the Expression objects inherit from an abstract class or an interface, and they all implement a method called interpret(Context). Each individual expression then interprets this context and returns a boolean value.
Let’s take an example of a customer wanting to place an order for a Table and Chair in a shop.
Instead of giving the user a basic form to fill in the values of the merchandise they want to order, we will give them an interactive form where they will enter the sentence to place the order. The form will look as follows.

Now, instead of a simple if-else condition, we will design a custom interpreter to find if the user wants both or just one of the merchandise. This way, in the future, if the string context changes, our interpreter-based rule will still apply.
import static java.lang.IO.println;
void main() {
String context = "I want to place order for chair and table both"; // customer demand
// rule 1: check if customer wants both
Expression customerAskingForBoth = buildAndExpressionInterpreter();
// rule 2: check if customer wants just one
Expression customerAskingForOne = buildOrExpressionInterpreter();
if (customerAskingForBoth.interpret(context)) {
println("Customer wants both Table and Chair");
}
if (customerAskingForOne.interpret(context)) {
println("Customer wants just one of Table or Chair");
}
}
static Expression buildOrExpressionInterpreter() {
Expression chairExpression = new ChairExpression();
Expression tableExpression = new TableExpression();
Expression orExpression = new OrExpression(chairExpression, tableExpression);
return orExpression;
}
static Expression buildAndExpressionInterpreter() {
Expression chairExpression = new ChairExpression();
Expression tableExpression = new TableExpression();
Expression andExpression = new AndExpression(chairExpression, tableExpression);
return andExpression;
}
The implementation of all the expressions are given below.
Click to view Expression
public interface Expression {
public boolean interpret(Object context);
}
Click to view TableExpression
import java.util.StringTokenizer;
public class TableExpression implements Expression{
@Override
public boolean interpret(Object context) {
if (context instanceof String) {
StringTokenizer st = new StringTokenizer((String) context);
while (st.hasMoreTokens()) {
if (st.nextToken().equalsIgnoreCase("table") ||
st.nextToken().equalsIgnoreCase("tables")) {
return true;
}
}
}
return false;
}
}
Click to view ChairExpression
import java.util.StringTokenizer;
public class ChairExpression implements Expression {
@Override
public boolean interpret(Object context) {
if (context instanceof String) {
StringTokenizer st = new StringTokenizer((String) context);
while (st.hasMoreTokens()) {
if (st.nextToken().equalsIgnoreCase("chair") ||
st.nextToken().equalsIgnoreCase("chairs")) {
return true;
}
}
}
return false;
}
}
Click to view OrExpression
public class OrExpression implements Expression{
private Expression exp1, exp2;
public OrExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public boolean interpret(Object context) {
boolean chairAndTableBoth = exp1.interpret(context) && exp2.interpret(context);
return !chairAndTableBoth && ((String) context).toLowerCase().contains("both");
}
}
Click to view AndExpression
public class AndExpression implements Expression{
private Expression exp1, exp2;
public AndExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public boolean interpret(Object context) {
boolean chairAndTableBoth = exp1.interpret(context) && exp2.interpret(context);
return chairAndTableBoth && ((String) context).toLowerCase().contains("both");
}
}
Here, AndExpression and OrExpression are compound or non-terminal expressions, whereas the TableExpression and ChairExpression are terminal expressions.
Real-World Examples
The Interpreter pattern is widely used in query languages, regular expression engines, and scripting tools. Two common examples in the Java ecosystem include:
1. Regular Expressions (java.util.regex.Pattern)
Java’s regex engine compiles regular expression strings into an internal structure of matching expressions. The Matcher then interprets these rules against the input string.
String input = "Lions, and tigers, and bears! Oh, my!";
// Compiles the regex string into an interpreter tree
Pattern p = Pattern.compile("(lion|cat|dog|wolf|bear)");
Matcher m = p.matcher(input);
while (m.find()) {
System.out.println("Found a " + m.group() + ".");
}
2. Spring Expression Language (SpEL)
Spring uses the Interpreter pattern to dynamically evaluate expressions at runtime. An expression like 'Hello World'.concat('!') is parsed into a tree of expression objects and interpreted against an evaluation context.
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();
Pitfalls
There are certain scenarios to consider before making use of this pattern.
- If the grammar and the rules gets too complex, it gets tedious to maintain in longer run
- Every class has to maintain it’s own rule.
- This pattern is specifically used for certain use case and is tightly bound to them which mostly include evaluating a sentence.
*Internally these might have different implementations but here they are mentioned for giving visualization based example.