Apache FreeMarker is a template engine: a Java library to generate text output based on templates and changing data.
—
FreeMarker provides a flexible system for loading templates from various sources and caching them for optimal performance. The template loading and caching system consists of template loaders, cache storage, and lookup strategies.
The core interface for loading template sources:
interface TemplateLoader {
Object findTemplateSource(String name) throws IOException;
long getLastModified(Object templateSource);
Reader getReader(Object templateSource, String encoding) throws IOException;
void closeTemplateSource(Object templateSource) throws IOException;
}
// Enhanced template loader with state information
interface StatefulTemplateLoader extends TemplateLoader {
void resetState();
}Loads templates from the file system:
class FileTemplateLoader implements TemplateLoader {
// Constructors
FileTemplateLoader(File baseDir) throws IOException;
FileTemplateLoader(File baseDir, boolean disableCanonicalization) throws IOException;
// TemplateLoader methods
Object findTemplateSource(String name) throws IOException;
long getLastModified(Object templateSource);
Reader getReader(Object templateSource, String encoding) throws IOException;
void closeTemplateSource(Object templateSource) throws IOException;
// Configuration methods
File getBaseDirectory();
boolean getEagerCanonicalization();
}Usage example:
// Load templates from /path/to/templates directory
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
cfg.setTemplateLoader(new FileTemplateLoader(new File("/path/to/templates")));
// Or use the convenience method
cfg.setDirectoryForTemplateLoading(new File("/path/to/templates"));Loads templates from the classpath:
class ClassTemplateLoader extends URLTemplateLoader {
// Constructors
ClassTemplateLoader(Class resourceLoaderClass, String basePackagePath);
// URLTemplateLoader methods
URL getURL(String name);
}Usage example:
// Load templates from classpath under /templates package
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
cfg.setTemplateLoader(new ClassTemplateLoader(MyClass.class, "/templates"));
// Or use the convenience method
cfg.setClassForTemplateLoading(MyClass.class, "/templates");Loads templates from strings stored in memory:
class StringTemplateLoader implements TemplateLoader {
// Constructor
StringTemplateLoader();
// Template management
void putTemplate(String name, String templateSource);
void putTemplate(String name, String templateSource, long lastModified);
void removeTemplate(String name);
void closeTemplateSource(Object templateSource) throws IOException;
// TemplateLoader methods
Object findTemplateSource(String name) throws IOException;
long getLastModified(Object templateSource);
Reader getReader(Object templateSource, String encoding) throws IOException;
}Usage example:
StringTemplateLoader loader = new StringTemplateLoader();
loader.putTemplate("hello.ftl", "<html><body>Hello ${name}!</body></html>");
loader.putTemplate("email.ftl", "Dear ${recipient}, Your order #${orderNumber} is ready.");
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
cfg.setTemplateLoader(loader);Combines multiple template loaders:
class MultiTemplateLoader implements StatefulTemplateLoader {
// Constructor
MultiTemplateLoader(TemplateLoader[] loaders);
// StatefulTemplateLoader methods
Object findTemplateSource(String name) throws IOException;
long getLastModified(Object templateSource);
Reader getReader(Object templateSource, String encoding) throws IOException;
void closeTemplateSource(Object templateSource) throws IOException;
void resetState();
// Configuration methods
TemplateLoader[] getTemplateLoaders();
}Usage example:
// Combine file system and classpath loading
TemplateLoader[] loaders = {
new FileTemplateLoader(new File("/custom/templates")),
new ClassTemplateLoader(MyClass.class, "/default/templates")
};
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
cfg.setTemplateLoader(new MultiTemplateLoader(loaders));Abstract base class for URL-based template loading:
abstract class URLTemplateLoader implements TemplateLoader {
// Abstract method to be implemented by subclasses
protected abstract URL getURL(String name);
// TemplateLoader implementation
Object findTemplateSource(String name) throws IOException;
long getLastModified(Object templateSource);
Reader getReader(Object templateSource, String encoding) throws IOException;
void closeTemplateSource(Object templateSource) throws IOException;
// Configuration methods
void setURLConnectionUsesCaches(Boolean urlConnectionUsesCaches);
Boolean getURLConnectionUsesCaches();
}Loads templates from servlet context:
class WebappTemplateLoader implements TemplateLoader {
// Constructors
WebappTemplateLoader(ServletContext servletContext);
WebappTemplateLoader(ServletContext servletContext, String subdirPath);
// TemplateLoader methods
Object findTemplateSource(String name) throws IOException;
long getLastModified(Object templateSource);
Reader getReader(Object templateSource, String encoding) throws IOException;
void closeTemplateSource(Object templateSource) throws IOException;
}Usage example:
// In a servlet context
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
cfg.setTemplateLoader(new WebappTemplateLoader(servletContext, "/WEB-INF/templates"));interface CacheStorage {
Object get(Object key);
void put(Object key, Object value);
void remove(Object key);
void clear();
}
// Enhanced cache storage with size information
interface CacheStorageWithGetSize extends CacheStorage {
int getSize();
}
// Thread-safe cache storage
interface ConcurrentCacheStorage extends CacheStorage {
// Marker interface for thread-safe implementations
}Most Recently Used cache with configurable size limits:
class MruCacheStorage implements CacheStorageWithGetSize {
// Constructors
MruCacheStorage(int maxStrongSize);
MruCacheStorage(int maxStrongSize, int maxSoftSize);
// CacheStorage methods
Object get(Object key);
void put(Object key, Object value);
void remove(Object key);
void clear();
int getSize();
// Configuration methods
int getMaxStrongSize();
int getMaxSoftSize();
}Usage example:
// Cache up to 100 templates with strong references, 1000 with soft references
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
cfg.setCacheStorage(new MruCacheStorage(100, 1000));Uses soft references for automatic memory management:
class SoftCacheStorage implements ConcurrentCacheStorage, CacheStorageWithGetSize {
// Constructor
SoftCacheStorage();
// CacheStorage methods
Object get(Object key);
void put(Object key, Object value);
void remove(Object key);
void clear();
int getSize();
}No automatic eviction - cached items remain until explicitly removed:
class StrongCacheStorage implements ConcurrentCacheStorage, CacheStorageWithGetSize {
// Constructor
StrongCacheStorage();
// CacheStorage methods
Object get(Object key);
void put(Object key, Object value);
void remove(Object key);
void clear();
int getSize();
}Disables caching completely:
class NullCacheStorage implements ConcurrentCacheStorage, CacheStorageWithGetSize {
// Singleton instance
static final NullCacheStorage INSTANCE = new NullCacheStorage();
// Constructor
NullCacheStorage();
// CacheStorage methods (all no-ops)
Object get(Object key);
void put(Object key, Object value);
void remove(Object key);
void clear();
int getSize();
}// Default cache (MRU with reasonable limits)
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
// Uses default MruCacheStorage
// Custom MRU cache
cfg.setCacheStorage(new MruCacheStorage(50, 200));
// Soft references cache (good for memory-constrained environments)
cfg.setCacheStorage(new SoftCacheStorage());
// Strong cache (never evicts, good for small template sets)
cfg.setCacheStorage(new StrongCacheStorage());
// Disable caching (useful for development)
cfg.setCacheStorage(NullCacheStorage.INSTANCE);Controls how templates are resolved and loaded:
abstract class TemplateLookupStrategy {
// Main lookup method
abstract TemplateLookupResult lookup(TemplateLookupContext ctx) throws IOException;
// Default strategy constants
static final TemplateLookupStrategy DEFAULT_2_3_0 = DefaultTemplateLookupStrategy.INSTANCE;
}
// Lookup context
class TemplateLookupContext {
String getTemplateName();
Locale getTemplateLocale();
String getCustomLookupCondition();
TemplateLoader getTemplateLoader();
}
// Lookup result
abstract class TemplateLookupResult {
abstract boolean isPositive();
}Controls template name formatting and normalization:
abstract class TemplateNameFormat {
// Name formatting methods
abstract String normalizeAbsoluteName(String name) throws MalformedTemplateNameException;
abstract String normalizeName(String name) throws MalformedTemplateNameException;
abstract String toAbsoluteName(String baseName, String targetName) throws MalformedTemplateNameException;
abstract String toRootBasedName(String baseName, String targetName) throws MalformedTemplateNameException;
// Default format constants
static final TemplateNameFormat DEFAULT_2_3_0 = DefaultTemplateNameFormat.INSTANCE_2_3_0;
static final TemplateNameFormat DEFAULT_2_4_0 = DefaultTemplateNameFormat.INSTANCE_2_4_0;
}Per-template configuration using matchers:
abstract class TemplateConfigurationFactory {
abstract TemplateConfiguration get(String sourceName, Object templateSource) throws IOException, TemplateConfigurationFactory.TemplateConfigurationFactoryException;
}
class ConditionalTemplateConfigurationFactory extends TemplateConfigurationFactory {
// Constructor
ConditionalTemplateConfigurationFactory(TemplateSourceMatcher matcher, TemplateConfigurationFactory factory);
TemplateConfiguration get(String sourceName, Object templateSource) throws IOException, TemplateConfigurationFactoryException;
}
// Template-specific configuration
class TemplateConfiguration extends Configurable implements ParserConfiguration {
TemplateConfiguration();
// Apply configuration to parent configurable
void apply(Configurable configurable);
}Match templates based on various criteria:
abstract class TemplateSourceMatcher {
abstract boolean matches(String sourceName, Object templateSource) throws IOException;
}
// Path glob matching
class PathGlobMatcher extends TemplateSourceMatcher {
PathGlobMatcher(String glob);
PathGlobMatcher(String glob, boolean caseSensitive);
boolean matches(String sourceName, Object templateSource) throws IOException;
}
// File extension matching
class FileExtensionMatcher extends TemplateSourceMatcher {
FileExtensionMatcher(String extension);
FileExtensionMatcher(String extension, boolean caseSensitive);
boolean matches(String sourceName, Object templateSource) throws IOException;
}
// OR matcher - matches if any submatcher matches
class OrMatcher extends TemplateSourceMatcher {
OrMatcher(TemplateSourceMatcher... matchers);
boolean matches(String sourceName, Object templateSource) throws IOException;
}Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
// Configure specific output format for HTML templates
TemplateConfiguration htmlConfig = new TemplateConfiguration();
htmlConfig.setOutputFormat(HTMLOutputFormat.INSTANCE);
htmlConfig.setAutoEscapingPolicy(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY);
cfg.setTemplateConfigurations(
new ConditionalTemplateConfigurationFactory(
new FileExtensionMatcher("html"),
new FirstMatchTemplateConfigurationFactory(htmlConfig)
)
);
// Configure different settings for email templates
TemplateConfiguration emailConfig = new TemplateConfiguration();
emailConfig.setOutputFormat(PlainTextOutputFormat.INSTANCE);
emailConfig.setWhitespaceStripping(true);
cfg.setTemplateConfigurations(
new ConditionalTemplateConfigurationFactory(
new PathGlobMatcher("email/**"),
new FirstMatchTemplateConfigurationFactory(emailConfig)
)
);// In Configuration class
void clearTemplateCache();
void removeTemplateFromCache(String name);
void removeTemplateFromCache(String name, Locale locale);
void removeTemplateFromCache(String name, Locale locale, String encoding);
void removeTemplateFromCache(String name, Locale locale, String encoding, boolean parseAsFTL);
// Cache statistics (if supported by cache storage)
CacheStorage getCacheStorage();Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
MruCacheStorage cache = new MruCacheStorage(100, 500);
cfg.setCacheStorage(cache);
// Monitor cache size
System.out.println("Cache size: " + cache.getSize());
System.out.println("Max strong size: " + cache.getMaxStrongSize());
// Clear specific templates from cache
cfg.removeTemplateFromCache("outdated-template.ftl");
// Clear entire cache
cfg.clearTemplateCache();Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
// Optimized for high-throughput applications
cfg.setTemplateLoader(new FileTemplateLoader(new File("templates")));
cfg.setCacheStorage(new MruCacheStorage(500, 2000)); // Large cache
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
cfg.setLogTemplateExceptions(false); // Reduce logging overhead
cfg.setWrapUncheckedExceptions(true);
// Pre-load critical templates
cfg.getTemplate("header.ftl");
cfg.getTemplate("footer.ftl");
cfg.getTemplate("main-layout.ftl");public class DatabaseTemplateLoader implements TemplateLoader {
private final DataSource dataSource;
public DatabaseTemplateLoader(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Object findTemplateSource(String name) throws IOException {
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(
"SELECT content, last_modified FROM templates WHERE name = ?")) {
stmt.setString(1, name);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return new DatabaseTemplateSource(
name,
rs.getString("content"),
rs.getTimestamp("last_modified").getTime()
);
}
return null;
} catch (SQLException e) {
throw new IOException("Failed to load template: " + name, e);
}
}
@Override
public long getLastModified(Object templateSource) {
return ((DatabaseTemplateSource) templateSource).getLastModified();
}
@Override
public Reader getReader(Object templateSource, String encoding) throws IOException {
String content = ((DatabaseTemplateSource) templateSource).getContent();
return new StringReader(content);
}
@Override
public void closeTemplateSource(Object templateSource) throws IOException {
// Nothing to close for database templates
}
private static class DatabaseTemplateSource {
private final String name;
private final String content;
private final long lastModified;
DatabaseTemplateSource(String name, String content, long lastModified) {
this.name = name;
this.content = content;
this.lastModified = lastModified;
}
String getContent() { return content; }
long getLastModified() { return lastModified; }
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-freemarker--freemarker