# jinjava: HubSpot's Jinja engine for Java, and the resource loader you have to think about

> jinjava renders Jinja templates from Java, forked originally from jangod and used in production to render thousands of HubSpot CMS sites. Its two sharp edges are explicit: it implements the subset of Jinja that HubSpot content uses, and its template loading is a security decision you have to make yourself.

**HubSpot/jinjava** — Jinja template engine for Java

- Repository: https://github.com/HubSpot/jinjava
- Stars: 789 · Forks: 185
- Language: Java
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/hubspot-jinjava

## A Java port of Jinja, scoped to what HubSpot needed

jinjava is a Java-based template engine based on django template syntax, adapted to render jinja templates, with the README being precise about the scope: at least the subset of jinja in use in HubSpot content.

That parenthetical is the most important sentence in the document. This is not a full Jinja implementation, it is a Java implementation of the parts of Jinja that a specific large product needed, and the scaling claim that follows explains why it exists: it is currently used in production to render thousands of websites with hundreds of millions of page views per month on the HubSpot CMS.

The lineage is documented too. It requires Java 8 or newer, and it was originally forked from jangod, a Django template engine in Java. So the architecture is a Django-style template language implemented for a JVM, extended towards Jinja syntax as HubSpot's content required it.

The project is published to Maven Central under the group and artifact com.hubspot.jinjava:jinjava, and the version is tracked in CHANGES.md in the repository root. The tagged releases are jinjava-2.8.4 on 2026-07-24, jinjava-2.8.3 on 2026-02-02 and jinjava-2.7.6 on the same February day. The last push to the repository was on 2026-10-17 and the repository is not archived.

The licence is Apache 2.0, with the LICENSE file at the root.

## Rendering a template is four lines of Java

The example in the README is deliberately small, and it shows the shape of the whole API. The template is an ordinary HTML file with a Jinja expression in it:

```html
<div>Hello, {{ name }}!</div>
```

The Java side creates an engine, puts a value into a context map, reads the template and renders it:

```java
Jinjava jinjava = new Jinjava();
Map<String, Object> context = Maps.newHashMap();
context.put("name", "Jared");

String template = Resources.toString(Resources.getResource("my-template.html"), Charsets.UTF_8);

String renderedTemplate = jinjava.render(template, context);
```

The result is the template with the expression substituted. There is no compile step, no template file format, no cache to warm and no configuration to get right for the simple case, which is why the example fits in a paragraph.

The context is an ordinary map, so anything a template can reach is something you put in the map. The template itself is passed as a string to render(), which means the engine does not have to own the template lifecycle, and the advanced section below shows how you hand it a template repository when you do want includes and inheritance to work.

The Maven dependency to add is:

```xml
  <dependency>
    <groupId>com.hubspot.jinjava</groupId>
    <artifactId>jinjava</artifactId>
    <version>{ LATEST_VERSION }</version>
  </dependency>
```

where LATEST_VERSION is the latest version from the CHANGES file, and there is a separate artifact version, 2.0.11-java7, for anyone stuck on Java 7.

## Template loading is the part you have to design

The advanced topics section opens with template loading, and it is the part of the documentation that deserves the most attention, because it is where a mistake becomes a vulnerability.

Jinjava needs to know how to interpret template paths so it can handle tags like extends with a template name. By default it loads only a ClasspathResourceLocator, which allows loading from any file in the classpath including class files. If you want Jinjava to load any file from the file system, you can add a FileResourceLocator, and the README attaches a warning: be aware the security risks of allowing user input to prevent a user from adding code such as an include of a path outside the template tree.

The correct reading is that the include tag takes a path and resolves it through the loader, so a loader that can reach the file system turns a template string into a file disclosure. The default is safe precisely because the classpath is bounded, even though it is a broad classpath.

For real applications the README recommends providing your own implementation of ResourceLoader to hook into your application's template repository, and then telling jinjava about it:

```java
JinjavaConfig config = JinjavaConfig.builder().build();

Jinjava jinjava = new Jinjava(config);
jinjava.setResourceLocator(new MyCustomResourceLocator());
```

And to use more than one ResourceLocator, use a CascadingResourceLocator:

```java
JinjavaConfig config = JinjavaConfig.builder().build();

Jinjava jinjava = new Jinjava(config);
jinjava.setResourceLocator(new MyCustomResourceLocator(), new FileResourceLocator());
```

So the extension point is the same in both cases: the application decides which paths a template can name, and Jinjava only follows the rules the application gave it.

## Custom tags, filters and functions are registered on the global context

You can provide custom jinja tags, filters and static functions to the engine, and the registration API is on the global context of the Jinjava instance.

```java
// define a custom tag implementing com.hubspot.jinjava.lib.Tag
jinjava.getGlobalContext().registerTag(new MyCustomTag());
// define a custom filter implementing com.hubspot.jinjava.lib.Filter
jinjava.getGlobalContext().registerFilter(new MyAwesomeFilter());
// define a custom public static function (this one will bind to myfn:my_func('foo', 42))
jinjava.getGlobalContext().registerFunction(new ELFunctionDefinition("myfn", "my_func", 
    MyFuncsClass.class, "myFunc", String.class, Integer.class);
```

The shape of each is worth reading. A tag is a class implementing the library's Tag interface, and a filter implements the Filter interface. A function is different: it is described by an ELFunctionDefinition that names a namespace and a function name, the class that holds the implementation, the Java method name, and the parameter types. The README notes that this one binds to a call in the form myfn:my_func('foo', 42), which is the expression language's namespaced function syntax.

There is also a registration call for classes that extend Importable, which is the mechanism for making your own objects usable from within a template rather than only as plain map values.

Taken together with the resource loader, these are the two integration points that matter. The loader decides where a template can reach, and the registrations decide what a template can do. Everything else about the engine is the template language itself.

## The repository is a Maven project with the usual Java hygiene

The repository layout is conventional for a Java library of this age, and the files in it tell you how the project is built and checked.

There is a pom.xml at the root with the Maven Wrapper alongside it in mvnw and mvnw.cmd and a.mvn/ directory, so a contributor can build without a preinstalled Maven. There is a checkstyle.xml for the coding standard and a spotbugs-exclude-filter.xml, which together are the pair of static checks a Java library usually grows.

Two configuration files are worth a mention because they are HubSpot specific. The.blazar-enabled marker and.blazar.yaml point at Blazar, HubSpot's own deployment system, so the project publishes to an internal pipeline as well as to Maven Central. The.prettier-java-enabled marker says that Java formatting is handled by prettier, which is why the repository has a so Java formatting is handled outside checkstyle.

There is a.gitmodules file as well, and a CHANGES.md that the README points to as the place to find the latest version, and a CONTRIBUTING.md.

Two more details about the state of the project. The release cadence is roughly twice a year rather than monthly, with 2.8.4 in July 2026 and two releases on the same day in February 2026, which suggests releases happen when a batch of work is finished rather than on a schedule. And the Maven Central badge is the canonical distribution route, so an adopter pulling the dependency from a repository rather than from source is the normal case.

## Where jinjava is the wrong tool

The first wrong fit is a team that needs the whole of Jinja. The README says the engine renders the subset of jinja in use in HubSpot content, and the feature set in the documentation is the one a content management system needs. If your templates use Jinja features that a general purpose implementation supports and this one does not, you will find out from a missing tag rather than from documentation, and the project gives you no feature matrix to check first.

The second is a multi-tenant system that lets a user name a template. This is the security case the README warns about. The default classpath loader is bounded, but the moment a FileResourceLocator is added and a template path comes from user input, an include can reach a file outside the template tree. The fix is to write your own ResourceLoader, and the README recommends exactly that, but the responsibility is yours and it is easy to skip.

The third is a team that wants a maintained, standards-tracked template engine. This is a fork of jangod maintained by HubSpot for HubSpot's product. The releases are infrequent, the repository has no GitHub releases, and there is no stated policy beyond the changelog. That is fine for a product team with a stake in it, and less comfortable for a platform team that needs a long support window.

The fourth is an application already on a modern Java baseline that has a template engine. jinjava is a small dependency and it does one job, but it is a fork of a fork, and the Java 7 fallback artifact is a reminder of how far back it reaches.

The fifth is anyone expecting it to be the Python Jinja. The syntax overlaps, the semantics do not, and the divergences are undocumented, so the two should not be assumed interchangeable even for a template that renders correctly on one of them.

## Conclusion

jinjava fits a Java team that has to render Jinja templates, especially one migrating from Python and carrying templates with it, and that is willing to implement its own resource locator. It does not fit a team that needs the full Jinja feature set, because the engine targets the subset HubSpot content uses, and it does not fit a multi-tenant setup that lets users name template paths without a loader that constrains them. Before adopting it, read the resource loading section and decide the loader first, because that choice determines whether template includes can reach the file system.

## FAQ

### What is jinjava?

It is a Java-based template engine based on django template syntax, adapted to render Jinja templates, at least the subset used in HubSpot content. It is used in production to render thousands of websites on the HubSpot CMS and was originally forked from jangod.

### How do I add jinjava to a Maven project?

Add the com.hubspot.jinjava:jinjava dependency with the version you want, taking the latest version number from the CHANGES file. If you are stuck on Java 7 there is a separate artifact version, 2.0.11-java7, and the library otherwise requires Java 8 or newer.

### How does jinjava resolve template paths?

Through a ResourceLoader. By default it uses a ClasspathResourceLocator that can load any file in the classpath, and a FileResourceLocator can be added for the file system, with the readme warning about the security risk of user input reaching paths such as an include of a file outside the template tree. You can also implement your own loader and, for several, use a CascadingResourceLocator.

### Can I add my own tags and filters to jinjava?

Yes. Tags implementing the library Tag interface and filters implementing the Filter interface are registered on the global context with registerTag and registerFilter, and static functions are registered with registerFunction using an ELFunctionDefinition that names a namespace, a method and its parameter types.

### What licence is jinjava released under?

It is licensed under the Apache 2.0 licence, with the LICENSE file at the root of the repository, and the recent releases are 2.8.4 on 2026-07-24 and 2.8.3 on 2026-02-02.

## Sources

- [HubSpot/jinjava on GitHub](https://github.com/HubSpot/jinjava)
- [Issues](https://github.com/HubSpot/jinjava/issues)
- [License: Apache-2.0](https://github.com/HubSpot/jinjava/blob/master/LICENSE)
- [README](https://github.com/HubSpot/jinjava/blob/master/README.md)
- [Releases](https://github.com/HubSpot/jinjava/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/hubspot-jinjava
