Use Velocity templates for notifications

Last modified 19 Mar 2026 13:48 +01:00
Since 3.4.1
This functionality is available since version 3.4.1.

You can use Apache Velocity when writing notifications and message templates. This article contains examples of using Apache Velocity for writing notifications, as well as how to access schema extension attributes in the Velocity code.

How to use Velocity

Technically, you can use Velocity templates in any place where an expression is expected, e.g., in mappings. However, since Velocity templates return textual (string) values, they are best fitted for string-based template creation.

Using Velocity templates is very simple for simple cases:

  • To show variable value in text, insert $variable in the template; e.g., $requestee.

  • You can go deeper into structured objects, e.g., $event.resourceOid.

  • If the variable could be confused with the immediate template content, use curly brackets: ${requestee} or ${event.resourceOid}

There are more complicated macros for iterating over collections, branching, adding comments, and much more. Check the Velocity User Guide.

Velocity 2.x is used in supported versions of midPoint. This may be an important detail when searching for solutions on the Internet.

Usage examples

Velocity templates in notification configuration
<!-- Send account password in plain text -->
<handler>
    <accountPasswordNotifier>
        <bodyExpression>
            <script>
                <language>http://midpoint.evolveum.com/xml/ns/public/expression/language#velocity</language>
                <code>Password for account $event.shadowName on $event.resourceName is: $event.plaintextPassword</code>
            </script>
        </bodyExpression>
        <transport>mail</transport>
    </accountPasswordNotifier>
</handler>

<!-- Send notification about account creation -->
<handler>
    <operation>add</operation>
    <status>success</status>
    <simpleResourceObjectNotifier>
        <bodyExpression>
            <script>
                <language>http://midpoint.evolveum.com/xml/ns/public/expression/language#velocity</language>
                <code>Notification about account-related operation

#if ($event.requesteeObject)Owner: $!event.requesteeDisplayName ($event.requesteeName, oid $event.requesteeOid)#end

Resource: $!event.resourceName (oid $event.resourceOid)

An account has been successfully created on the resource with attributes:
$event.contentAsFormattedList
Channel: $!event.channel</code>
            </script>
        </bodyExpression>
        <transport>mail</transport>
    </simpleResourceObjectNotifier>
</handler>

<!-- Send notification about account deletion -->
<handler>
    <operation>delete</operation>
    <status>success</status>
    <simpleResourceObjectNotifier>
        <bodyExpression>
            <script>
                <language>http://midpoint.evolveum.com/xml/ns/public/expression/language#velocity</language>
                <code>Notification about account-related operation

#if ($event.requesteeObject)Owner: $!event.requesteeDisplayName ($event.requesteeName, oid $event.requesteeOid)#end

Resource: $event.resourceName (oid $event.resourceOid)
Account: $!event.shadowName

The account has been successfully removed from the resource.

Channel: $!event.channel</code>
            </script>
        </bodyExpression>

        <transport>mail</transport>
    </simpleResourceObjectNotifier>
</handler>

<!-- Send notification about user creation, modification, and deletion, including details about the affected object -->
<handler>
    <simpleUserNotifier>
        <bodyExpression>
            <script>
                <language>http://midpoint.evolveum.com/xml/ns/public/expression/language#velocity</language>
                <code>Notification about ${event.focusTypeName.toLowerCase()}-related operation (status: $event.statusAsText)

$event.focusTypeName: $event.requesteeDisplayName ($event.requesteeName, oid $event.requesteeOid)

#if ($event.add)The ${event.focusTypeName.toLowerCase()} record was created with the following data:
$event.contentAsFormattedList
#elseif ($event.modify)The ${event.focusTypeName.toLowerCase()} record was modified. Modified attributes are:
$event.contentAsFormattedList
#elseif ($event.delete)The ${event.focusTypeName.toLowerCase()} record was deleted.
#end
Channel: $!event.channel</code>
            </script>
        </bodyExpression>
        <transport>mail</transport>
    </simpleUserNotifier>
</handler>

Note that since midPoint 4.5, you can use message templates and reference those from your configured notifiers.

Use extension attributes in Velocity templates

If you have a schema extension defined, accessing the attributes in from the extension requires a few extra steps compared to the built-in attributes.

The solution examples in this section deal with UserType extension attributes. However, schema extensions are not exclusive to focal objects of the UserType. You can face a similar challenge in Velocity templates with extension attributes of any other type, such as OrgType or ServiceType. See Other focal object type extensions for more information on dealing with extension attributes in other types.

The problem

Extension attributes on a user object cannot be accessed directly through $event.extension.

The extension property on an event object returns an internal Prism container value structure that Velocity cannot traverse. Attempting to access a field value with any of the following patterns does not work:

Invalid patterns for extension attributes access
$event.extension.myField
$event.extension["myField"]
$event.extension.get("http://example.com/userType").myField

$event.extension by itself renders as a debug dump of the structure, for example:

$event.extension debug dump
PCV(null):[PP({http://example.com/userType}Field1):[PPV(String:value1)], PP({http://example.com/userType}Field2):[PPV(String:value2)]]

This may be useful for configuration discovery but cannot be used to extract individual values.

The solution

Retrieve the full user object from the event using $event.requesteeObject, then read extension properties from it using the basic.getExtensionPropertyValue() method from the Basic library:

Correct way to access user schema extension attributes
#set($user = $event.requesteeObject)(1)
#set($activationCode = ${basic.getExtensionPropertyValue($user, 'activationCode')}) (2)

Your activation code is: $activationCode
1 $event.requesteeObject returns the full UserType object.
2 activationCode is the UserType schema extension attribute. getExtensionPropertyValue() takes the whole user object and the name of the extension property as a string, and returns its value.

For multivalued extension properties, use basic.getExtensionPropertyValues() to get a collection of values over which you can iterate with #foreach.

Below is a worked example of a simple user notifier which sends a notification every time a user focal object is modified. The notification contains the modified user’s names and office floor number, the latter being an attribute from a schema extension.

Simple user notifier example working both with built-in and extended schema attributes
<simpleUserNotifier>
    <name>user-modified-notification</name>
    <bodyExpression>
        <script>
            <language>http://midpoint.evolveum.com/xml/ns/public/expression/language#velocity</language>
            <code>
#set($user = $event.requesteeObject)
#set($officeFloor = ${basic.getExtensionPropertyValue($user, 'officeFloorNumber')})
User was modified in midPoint.

Name:        $user.name (1)
Given name:  $user.givenName (2)
Family name: $user.familyName (2)
#if($officeFloor)
Office floor number: $officeFloor (3)
#end
            </code>
        </script>
    </bodyExpression>
    <transport>
        <_value>file transport</_value>
    </transport>
</simpleUserNotifier>
1 The name in UserType is a PolyStringType, not a plain string. In a Velocity context, $user.name calls toString() on the Java object, which may render as an internal representation rather than the human-readable value. In such a case, use $user.name.orig which retrieves the primary (original) string explicitly.
2 Plain xsd:string built-in attributes requiring no special treatment.
3 The attribute from the schema extension obtained by the getExtensionPropertyValue() method.

Other focal object type extensions

basic.getExtensionPropertyValue() is not specific to UserType. Its first argument accepts any midPoint object, so the same calls work for OrgType, RoleType, ServiceType, and other extended types.

The difference lies in which notifier you use and how you obtain the focal object from the event. For notifications about focal objects other than users, use simpleFocalObjectNotifier instead of simpleUserNotifier. Based on the pattern established for simpleUserNotifier, retrieving the focal object via $event.requesteeObject is the expected approach, though you should verify the available variables.

Was this page helpful?
YES NO
Thanks for your feedback