- Introduction
- Built-in Functions
- String Functions
- Utility Functions
- Custom Functions
- Function Composition
JHP provides a set of built-in functions and allows you to register custom functions. Functions are called within expressions using standard function call syntax.
JHP includes several built-in functions that are always available.
Convert a string to uppercase.
Syntax:
{{ touppercase(string) }}Example:
{{ touppercase("hello world") }}
<!-- Output: HELLO WORLD -->
{{ touppercase(username) }}Java Context:
ctx.add("username", "alice");Output:
ALICE
Convert a string to lowercase.
Syntax:
{{ tolowercase(string) }}Example:
{{ tolowercase("HELLO WORLD") }}
<!-- Output: hello world -->
{{ tolowercase(email) }}Remove leading and trailing whitespace from a string.
Syntax:
{{ trim(string) }}Example:
{{ trim(" hello world ") }}
<!-- Output: hello world -->
{{ trim(userInput) }}Java Context:
ctx.add("userInput", " Alice ");Output:
Alice
Get the length of a string, collection, map, or array.
Syntax:
{{ len(value) }}Examples:
String Length:
{{ len("hello") }}
<!-- Output: 5 -->
{{ len(username) }}Collection Size:
{{ len(items) }}Java Context:
List<String> items = Arrays.asList("A", "B", "C");
ctx.add("items", items);Output:
3
Map Size:
{{ len(config) }}Array Length:
{{ len(numbers) }}Java Context:
int[] numbers = {1, 2, 3, 4, 5};
ctx.add("numbers", numbers);Output:
5
Get the current timestamp as an Instant object.
Syntax:
{{ now() }}Example:
<p>Generated at: {{ now() }}</p>
<!-- Output: Generated at: 2025-10-05T00:18:03.123456Z -->Note: The output format depends on the Instant.toString() method.
You can register custom functions to extend JHP's functionality.
FunctionLibrary lib = new FunctionLibrary();
// Register a simple function
lib.register("double", (args, scopes) -> {
if (args.isEmpty()) return 0;
Number n = (Number) args.get(0);
return n.doubleValue() * 2;
});
JhpEngine engine = new JhpEngine(settings, lib);Template:
{{ double(5) }}
<!-- Output: 10.0 -->lib.register("add", (args, scopes) -> {
if (args.size() < 2) return 0;
Number a = (Number) args.get(0);
Number b = (Number) args.get(1);
return a.doubleValue() + b.doubleValue();
});Template:
{{ add(10, 20) }}
<!-- Output: 30.0 -->lib.register("reverse", (args, scopes) -> {
if (args.isEmpty() || args.get(0) == null) return "";
String str = args.get(0).toString();
return new StringBuilder(str).reverse().toString();
});Template:
{{ reverse("hello") }}
<!-- Output: olleh -->Functions can access the scope stack to read variables:
lib.register("greet", (args, scopes) -> {
String name = "Guest";
// Search through scopes for 'username' variable
for (Map<String, Object> scope : scopes) {
if (scope.containsKey("username")) {
name = scope.get("username").toString();
break;
}
}
return "Hello, " + name + "!";
});Template:
{{ greet() }}
<!-- Output: Hello, Alice! (if username is in context) -->lib.register("pluralize", (args, scopes) -> {
if (args.size() < 2) return "";
Number count = (Number) args.get(0);
String singular = args.get(1).toString();
String plural = args.size() > 2 ? args.get(2).toString() : singular + "s";
return count.intValue() == 1 ? singular : plural;
});Template:
{{ count }} {{ pluralize(count, "item") }}
<!-- 1 item, 5 items -->
{{ count }} {{ pluralize(count, "person", "people") }}
<!-- 1 person, 5 people -->lib.unregister("myFunction");Custom functions take precedence over built-in functions:
// Override the built-in trim function
lib.register("trim", (args, scopes) -> {
if (args.isEmpty() || args.get(0) == null) return "";
return args.get(0).toString().trim().toUpperCase();
});You can nest function calls to create complex expressions:
{{ touppercase(trim(username)) }}
<!-- Trim then uppercase -->
{{ len(tolowercase(text)) }}
<!-- Convert to lowercase then get length -->
{{ touppercase(items[0]) + " - " + len(items) + " items" }}
<!-- Combine function calls with operators -->{% if (len(trim(comment)) > 0) %}
<p>{{ touppercase(trim(comment)) }}</p>
{% endif %}- Keep Functions Pure - Functions should not have side effects
- Handle Null Values - Always check for null arguments
- Return Appropriate Types - Return types that make sense in templates
- Use Descriptive Names - Function names should be clear and lowercase
- Document Custom Functions - Maintain documentation for your custom functions
- Error Handling - Handle errors gracefully within functions
lib.register("formatprice", (args, scopes) -> {
// Validate arguments
if (args.isEmpty() || args.get(0) == null) {
return "$0.00";
}
try {
// Parse number
Number price = (Number) args.get(0);
// Format with 2 decimal places
return String.format("$%.2f", price.doubleValue());
} catch (Exception e) {
// Return safe default on error
return "$0.00";
}
});Template:
<span class="price">{{ formatprice(product.price) }}</span>Function names are case-insensitive:
{{ TOUPPERCASE(text) }}
{{ ToUpperCase(text) }}
{{ touppercase(text) }}
<!-- All work the same -->If a function throws an exception, the behavior depends on the configured IssueHandleMode:
- THROW - Exception is propagated
- COMMENT - Error is rendered as HTML comment
- DEBUG - Detailed error information is displayed
- IGNORE - Error is silently ignored
See Configuration for more details.
- Learn about Context & Variables
- Explore Configuration options
- Review Advanced Usage