How can I introspect a freemarker template to find out what variables it uses?

I had the same task to get the list of variables from template on java side and don't found any good approaches to that except using reflection. I'm not sure whether there is a better way to get this data or not but here's my approach:

public Set<String> referenceSet(Template template) throws TemplateModelException {
    Set<String> result = new HashSet<>();
    TemplateElement rootTreeNode = template.getRootTreeNode();
    for (int i = 0; i < rootTreeNode.getChildCount(); i++) {
        TemplateModel templateModel = rootTreeNode.getChildNodes().get(i);
        if (!(templateModel instanceof StringModel)) {
            continue;
        }
        Object wrappedObject = ((StringModel) templateModel).getWrappedObject();
        if (!"DollarVariable".equals(wrappedObject.getClass().getSimpleName())) {
            continue;
        }

        try {
            Object expression = getInternalState(wrappedObject, "expression");
            switch (expression.getClass().getSimpleName()) {
                case "Identifier":
                    result.add(getInternalState(expression, "name").toString());
                    break;
                case "DefaultToExpression":
                    result.add(getInternalState(expression, "lho").toString());
                    break;
                case "BuiltinVariable":
                    break;
                default:
                    throw new IllegalStateException("Unable to introspect variable");
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new TemplateModelException("Unable to reflect template model");
        }
    }
    return result;
}

private Object getInternalState(Object o, String fieldName) throws NoSuchFieldException, IllegalAccessException {
    Field field = o.getClass().getDeclaredField(fieldName);
    boolean wasAccessible = field.isAccessible();
    try {
        field.setAccessible(true);
        return field.get(o);
    } finally {
        field.setAccessible(wasAccessible);
    }
}

Sample project that I made for demonstrating template introspection can be found on github: https://github.com/SimY4/TemplatesPOC.git


This is probably late, but in case anyone else encountered this problem : you can use 'data_model' and 'globals' to inspect the model - data_model will only contain values provided by the model while globals will also contain any variables defined in the template. You need to prepend the special variables with a dot - so to access globals, use ${.globals}

For other special variables see http://freemarker.sourceforge.net/docs/ref_specvar.html


one other way to get the variables from java. This just tries to process the template and catch the InvalidReferenceException to find all the variables in a freemarker-template

 /**
 * Find all the variables used in the Freemarker Template
 * @param templateName
 * @return
 */
public Set<String> getTemplateVariables(String templateName) {
    Template template = getTemplate(templateName);
    StringWriter stringWriter = new StringWriter();
    Map<String, Object> dataModel = new HashMap<>();
    boolean exceptionCaught;

    do {
        exceptionCaught = false;
        try {
            template.process(dataModel, stringWriter);
        } catch (InvalidReferenceException e) {
            exceptionCaught = true;
            dataModel.put(e.getBlamedExpressionString(), "");
        } catch (IOException | TemplateException e) {
            throw new IllegalStateException("Failed to Load Template: " + templateName, e);
        }
    } while (exceptionCaught);

    return dataModel.keySet();
}

private Template getTemplate(String templateName) {
    try {
        return configuration.getTemplate(templateName);
    } catch (IOException e) {
        throw new IllegalStateException("Failed to Load Template: " + templateName, e);
    }
}