I went through the Crafting Interpreters book with modern Java. I posted this on reddit, but I basically used records and sealed interfaces. With modern Java there's no need for visitor pattern.
private void execute(Stmt statement) {
switch (statement) {
case Stmt.Expression expression -> evaluate(expression.expr());
case Stmt.Block block -> executeBlock(block.statements(),
new Environment(environment));
...
public sealed interface Expr permits
Expr.Assign,
Expr.Binary,
Expr.Call,
Expr.Function,
.... more exprs here