Skip to main content

How do I stop a property-update rule from firing when the node is being created?

Question

How do I stop a property-update rule from firing when the node is being created?

Answer

Creating a node also sets its initial property values, and each of those is a property event. So a rule written as "when jcr:title changes" fires during creation as well as on a later edit.

There is no isNew() or "is creation" flag on any Jahia rule fact. The way to express it is a negative condition: this property changed, and no node was added at the same path in this batch.

Jahia ships a DSL sentence for exactly that:

The {node} has not been added  =  not AddedNodeFact ( path == ({node}.getPath()) )

The three rules

rule "nodeA created"
    when
        A new node is created
            - the node has the type mynt:nodeA
    then
        > logger.info("created {}", node.getPath());
end

rule "nodeA jcr:title updated, but not at creation"
    when
        A property jcr:title has been set on a node
            - the node has the type mynt:nodeA
        The node has not been added
    then
        > logger.info("title updated on existing {}", node.getPath());
end

rule "nodeA deleted"
    when
        A node is deleted
            - the node has the type mynt:nodeA
    then
        > logger.info("deleted {}", node.getPath());
end

The node has not been added is a standalone pattern and must be on its own line - it is not a - constraint fragment appended to the sentence above it.

In raw DRL, without the DSL:

rule "nodeA jcr:title updated, but not at creation"
    when
>       property : ChangedPropertyFact ( name == "jcr:title", node : node, node.types contains "mynt:nodeA" )
>       not AddedNodeFact ( path == ( node.getPath() ) )
    then
>       logger.info("title updated on existing {}", node.getPath());
end

Why it works

RulesListener collects every JCR event from one session.save() into a single list - the AddedNodeFact for the new node and the ChangedPropertyFact for each initial property - and only then executes the rules, through a stateless session that inserts all facts before firing any rule. So when the update rule is evaluated during a creation, the AddedNodeFact is already in working memory and the not condition is false.

The caveat that will catch you out

The guard works per session.save(). If your code does:

node.addNode("a", "mynt:nodeA");
session.save();                        // batch 1: AddedNodeFact
node.setProperty("jcr:title", "x");
session.save();                        // batch 2: no AddedNodeFact

then the second batch has no AddedNodeFact, and the update rule will fire. Content Editor and jContent create a node and its properties in a single save, so the guard holds for normal authoring - but not necessarily for your own code.

Two more worth knowing:

  • Adding a new language to an existing node fires the update rule. The property lives on a jnt:translation child, and RulesListener deliberately does not emit an AddedNodeFact for translation nodes. This is usually what you want, but it is not "creation" in the sense most people mean.
  • Some properties never produce a rule event at all, including jcr:primaryType, jcr:uuid, jcr:created, jcr:createdBy, jcr:lastModified and jcr:lastModifiedBy. If your rule never fires, check it is not watching one of those.

Do not use getOperationType() for this

getOperationType() exists on the facts, but it does not distinguish creation from update. It returns only "import", "session", "clone" or null.

A related trap: a condition such as - not in operation copy is always true, because "copy" is never produced. It looks like a guard and does nothing. This mistake is present in at least one real Jahia module, so do not copy it from existing code.

Measured

Reproduced on Jahia 8.2.3.2 by loading the three rules plus a deliberately unguarded copy of the update rule, so the guarded and unguarded versions could be compared in the same run, and watching which fired.

Scenariocreate ruleupdate rule, unguardedupdate rule, guarded
Node created with jcr:title in one savefiredfireddid not fire
jcr:title edited later-firedfired
Node created, then titled in a second savefiredfiredfired

Row one is the problem and the fix together: creating the node fires the update rule, and the guard suppresses exactly that firing. Row two confirms the guard does not suppress anything else - a real edit still fires it.

Row three is the caveat, measured rather than predicted. Split the work across two session.save() calls and the guard stops helping, because the second batch contains no AddedNodeFact. If your code creates a node and sets its properties in separate saves, this will not do what you want.

One thing to know before you rely on it

The DSL sentence is supplied by the platform and compiles - the rules above were loaded by Jahia without a parser error. But no rule shipped with Jahia or with any public Jahia module uses it, so it is not a pattern the platform exercises on its own behalf. It behaved correctly in the three cases above; test it against your own content model rather than assuming it generalises.


This article was drafted with AI assistance, then reviewed and curated by Jahia Customer Support engineers before publication.