Apache FreeMarker is a template engine: a Java library to generate text output based on templates and changing data.
—
Object wrapping is the process of converting Java objects into TemplateModel objects that can be used in FreeMarker templates. The ObjectWrapper interface and its implementations provide this functionality.
interface ObjectWrapper {
TemplateModel wrap(Object obj) throws TemplateModelException;
}
// Enhanced object wrapper with unwrapping capabilities
interface RichObjectWrapper extends ObjectWrapper {
TemplateModel wrap(Object obj) throws TemplateModelException;
}The DefaultObjectWrapper is the recommended object wrapper for most use cases. It extends BeansWrapper and adds support for DOM nodes.
class DefaultObjectWrapper extends BeansWrapper {
// Constructors
DefaultObjectWrapper(Version incompatibleImprovements);
// Factory method for default instance
static DefaultObjectWrapper getDefaultInstance();
// Wrapping methods
TemplateModel wrap(Object obj) throws TemplateModelException;
// Configuration inherited from BeansWrapper
void setExposeFields(boolean exposeFields);
boolean isExposeFields();
void setExposureLevel(int exposureLevel);
int getExposureLevel();
}// Create with specific version
DefaultObjectWrapper wrapper = new DefaultObjectWrapper(Configuration.VERSION_2_3_34);
// Use default instance (shared, thread-safe)
DefaultObjectWrapper wrapper = DefaultObjectWrapper.getDefaultInstance();
// Configure in Configuration
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
cfg.setObjectWrapper(wrapper);For advanced configuration, use the builder pattern:
class DefaultObjectWrapperBuilder extends DefaultObjectWrapperConfiguration {
// Constructor
DefaultObjectWrapperBuilder(Version incompatibleImprovements);
// Build method
DefaultObjectWrapper build();
// Configuration methods
DefaultObjectWrapperBuilder setExposeFields(boolean exposeFields);
DefaultObjectWrapperBuilder setExposureLevel(int exposureLevel);
DefaultObjectWrapperBuilder setForceLegacyNonListCollections(boolean force);
DefaultObjectWrapperBuilder setDefaultDateType(int defaultDateType);
DefaultObjectWrapperBuilder setOuterIdentity(ObjectWrapperAndUnwrapper outerIdentity);
DefaultObjectWrapperBuilder setStrict(boolean strict);
DefaultObjectWrapperBuilder setUseModelCache(boolean useModelCache);
DefaultObjectWrapperBuilder setWrapAsHashModel(boolean wrapAsHashModel);
}DefaultObjectWrapper wrapper = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_34)
.setExposeFields(true)
.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY)
.setForceLegacyNonListCollections(false)
.setDefaultDateType(TemplateDateModel.DATETIME)
.build();
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
cfg.setObjectWrapper(wrapper);The BeansWrapper is the foundation for Java bean wrapping, providing access to properties, methods, and fields.
class BeansWrapper implements RichObjectWrapper, WriteProtectable {
// Constructors
BeansWrapper();
BeansWrapper(Version incompatibleImprovements);
// Core wrapping methods
TemplateModel wrap(Object obj) throws TemplateModelException;
Object unwrap(TemplateModel model) throws TemplateModelException;
Object unwrap(TemplateModel model, Class targetClass) throws TemplateModelException;
// Static and enum model access
TemplateHashModel getStaticModels();
TemplateHashModel getEnumModels();
// Field exposure configuration
void setExposeFields(boolean exposeFields);
boolean isExposeFields();
// Exposure level control
void setExposureLevel(int exposureLevel);
int getExposureLevel();
// Method appearance customization
void setMethodAppearanceFineTuner(MethodAppearanceFineTuner tuner);
MethodAppearanceFineTuner getMethodAppearanceFineTuner();
// Security and access control
void setSecurityManager(SecurityManager securityManager);
SecurityManager getSecurityManager();
// Null model handling
void setNullModel(TemplateModel nullModel);
TemplateModel getNullModel();
// Date type configuration
void setDefaultDateType(int defaultDateType);
int getDefaultDateType();
// Outer identity (for unwrapping)
void setOuterIdentity(ObjectWrapperAndUnwrapper outerIdentity);
ObjectWrapperAndUnwrapper getOuterIdentity();
// Strict bean access
void setStrict(boolean strict);
boolean isStrict();
// Model caching
void setUseModelCache(boolean useCache);
boolean getUseModelCache();
void clearModelCache();
// Constants for exposure levels
static final int EXPOSE_ALL = 0;
static final int EXPOSE_PROPERTIES_ONLY = 1;
static final int EXPOSE_NOTHING = 2;
}// Basic beans wrapper setup
BeansWrapper wrapper = new BeansWrapper(Configuration.VERSION_2_3_34);
wrapper.setExposeFields(true);
wrapper.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);
// Access static methods and fields
TemplateHashModel staticModels = wrapper.getStaticModels();
TemplateModel stringClass = staticModels.get("java.lang.String");
// In template: ${statics["java.lang.String"].valueOf(123)}
// Access enum constants
TemplateHashModel enumModels = wrapper.getEnumModels();
TemplateModel dayOfWeek = enumModels.get("java.time.DayOfWeek");
// In template: ${enums["java.time.DayOfWeek"].MONDAY}
// Custom method appearance
wrapper.setMethodAppearanceFineTuner(new MethodAppearanceFineTuner() {
public void process(MethodAppearanceDecision decision, Method method, Class clazz) {
if (method.getName().startsWith("internal")) {
decision.setExposeMethodAs(null); // Hide internal methods
}
}
});class StaticModels implements TemplateHashModel {
StaticModels(BeansWrapper wrapper);
TemplateModel get(String key) throws TemplateModelException;
boolean isEmpty() throws TemplateModelException;
}
// Individual static model for a class
class StaticModel implements TemplateHashModel, TemplateScalarModel {
StaticModel(Class clazz, BeansWrapper wrapper);
TemplateModel get(String key) throws TemplateModelException;
boolean isEmpty() throws TemplateModelException;
String getAsString() throws TemplateModelException;
}// Setup static models access
BeansWrapper wrapper = new BeansWrapper(Configuration.VERSION_2_3_34);
TemplateHashModel staticModels = wrapper.getStaticModels();
Map<String, Object> dataModel = new HashMap<>();
dataModel.put("statics", staticModels);
// In template, access static methods:
// ${statics["java.lang.Math"].max(10, 20)}
// ${statics["java.util.Collections"].emptyList()}
// ${statics["java.lang.System"].currentTimeMillis()}class EnumModels implements TemplateHashModel {
EnumModels(BeansWrapper wrapper);
TemplateModel get(String key) throws TemplateModelException;
boolean isEmpty() throws TemplateModelException;
}
// Individual enum model
class EnumModel extends StaticModel {
EnumModel(Class enumClass, BeansWrapper wrapper);
}// Setup enum models access
BeansWrapper wrapper = new BeansWrapper(Configuration.VERSION_2_3_34);
TemplateHashModel enumModels = wrapper.getEnumModels();
Map<String, Object> dataModel = new HashMap<>();
dataModel.put("enums", enumModels);
// In template, access enum constants:
// ${enums["java.time.DayOfWeek"].MONDAY}
// ${enums["java.util.concurrent.TimeUnit"].SECONDS}For simpler use cases that don't require bean introspection:
class SimpleObjectWrapper extends DefaultObjectWrapper {
SimpleObjectWrapper();
SimpleObjectWrapper(boolean simpleMapWrapper);
TemplateModel wrap(Object obj) throws TemplateModelException;
}// Simple wrapper that uses only Simple* model classes
SimpleObjectWrapper wrapper = new SimpleObjectWrapper();
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
cfg.setObjectWrapper(wrapper);
// This wrapper will convert:
// - Strings to SimpleScalar
// - Numbers to SimpleNumber
// - Booleans to TemplateBooleanModel.TRUE/FALSE
// - Collections to SimpleSequence
// - Maps to SimpleHash
// - Other objects using toString() to SimpleScalarinterface MethodAppearanceFineTuner {
void process(MethodAppearanceDecision decision, Method method, Class clazz);
}
class MethodAppearanceDecision {
void setExposeMethodAs(String name);
void setMethodShadowsProperty(boolean shadows);
void setReplaceExistingProperty(boolean replace);
String getExposeMethodAs();
boolean getMethodShadowsProperty();
boolean getReplaceExistingProperty();
}BeansWrapper wrapper = new BeansWrapper(Configuration.VERSION_2_3_34);
wrapper.setMethodAppearanceFineTuner(new MethodAppearanceFineTuner() {
public void process(MethodAppearanceDecision decision, Method method, Class clazz) {
String methodName = method.getName();
// Hide methods starting with underscore
if (methodName.startsWith("_")) {
decision.setExposeMethodAs(null);
}
// Rename getXXX methods to just XXX for cleaner template access
if (methodName.startsWith("get") && methodName.length() > 3) {
String propertyName = methodName.substring(3);
propertyName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
decision.setExposeMethodAs(propertyName);
decision.setMethodShadowsProperty(false);
}
// Hide deprecated methods
if (method.isAnnotationPresent(Deprecated.class)) {
decision.setExposeMethodAs(null);
}
}
});class BeanModel implements TemplateHashModel, AdapterTemplateModel, WrapperTemplateModel {
BeanModel(Object object, BeansWrapper wrapper);
TemplateModel get(String key) throws TemplateModelException;
boolean isEmpty() throws TemplateModelException;
Object getAdaptedObject(Class hint);
Object getWrappedObject();
BeansWrapper getBeansWrapper();
}class StringModel extends BeanModel implements TemplateScalarModel {
StringModel(Object object, BeansWrapper wrapper);
String getAsString() throws TemplateModelException;
}class NumberModel extends BeanModel implements TemplateNumberModel {
NumberModel(Number number, BeansWrapper wrapper);
Number getAsNumber() throws TemplateModelException;
}class DateModel extends BeanModel implements TemplateDateModel, TemplateHashModel {
DateModel(Date date, BeansWrapper wrapper);
DateModel(Date date, BeansWrapper wrapper, int type);
Date getAsDate() throws TemplateModelException;
int getDateType();
}The DefaultObjectWrapper automatically wraps objects according to these rules:
TemplateModel.NOTHINGStringModel (extends both BeanModel and TemplateScalarModel)NumberModel (extends both BeanModel and TemplateNumberModel)DateModel (extends both BeanModel and TemplateDateModel)TemplateBooleanModel.TRUE or TemplateBooleanModel.FALSEDefaultMapAdapter (implements TemplateHashModelEx2)DefaultListAdapter (implements TemplateSequenceModel)DefaultArrayAdapter (implements TemplateSequenceModel)DefaultCollectionAdapter (implements TemplateCollectionModel)DefaultIteratorAdapter (implements TemplateCollectionModel)DefaultEnumerationAdapter (implements TemplateCollectionModel)NodeModel (if DOM support enabled)BeanModel (provides access to properties and methods)You can create custom object wrappers for specialized behavior:
public class CustomObjectWrapper extends DefaultObjectWrapper {
public CustomObjectWrapper(Version incompatibleImprovements) {
super(incompatibleImprovements);
}
@Override
public TemplateModel wrap(Object obj) throws TemplateModelException {
// Custom wrapping for specific types
if (obj instanceof MyCustomClass) {
return new MyCustomTemplateModel((MyCustomClass) obj);
}
// Fall back to default wrapping
return super.wrap(obj);
}
}BeansWrapper caches introspection results and model instances for better performance:
// Enable model caching (default: true)
wrapper.setUseModelCache(true);
// Clear cache when needed
wrapper.clearModelCache();DefaultObjectWrapper.getDefaultInstance() returns a shared, thread-safe instanceDifferent exposure levels affect performance:
// Fastest - only exposes properties
wrapper.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);
// Slower - exposes properties, methods, and fields
wrapper.setExposureLevel(BeansWrapper.EXPOSE_ALL);
// Fastest but most restrictive - no bean introspection
wrapper.setExposureLevel(BeansWrapper.EXPOSE_NOTHING);Install with Tessl CLI
npx tessl i tessl/maven-org-freemarker--freemarker