Skip to main content
Article

1 Is Not 1.0: XPath Arithmetic in Synapse, and Doing Without the Script Mediator

5 min read 53 views

A batch counter in a WSO2 Enterprise Integrator sequence incremented correctly, logged correctly, and still broke the one comparison that depended on it. The counter held 1.0, not 1, and a filter comparing it with the string '1' never matched. The defect had existed in a sibling integration for years, absorbed by a guard nobody had traced. This post explains where the .0 comes from, how to remove it with native mediators, and why we now prefer property, filter and switch over a new Script mediator for almost everything else.

Where the .0 comes from

<property name="BATCH_NUMBER" expression="$ctx:BATCH_NUMBER + 1"
          scope="default" type="STRING"/>

+ is XPath arithmetic, so the result is a number, and Synapse converts that number to a string through Java's Double.toString. The property holds 1.0. Declaring type="STRING" does not change the result; it only states how the result is stored.

In logs this is cosmetic: BATCH_NUMBER = 1.0, TOTAL_ROWS = 1234.0. In anything that compares or parses the value, it is not. In this case, the first batch of each load had to tell the target API to truncate the table before inserting:

<filter xpath="get-property('BATCH_NUMBER') = '1'">

With 1.0 in the property, that filter never matches, and the truncate never fires. Each run appends a full copy of the data to the previous one.

The guard that hid it

The older integration this one was modelled on contained:

<filter xpath="get-property('BATCH_NUMBER') = '1' or get-property('BATCH_NUMBER') = '1.0'">

That or '1.0' is the same defect, worked around at the comparison and never fixed at the source. It is harmless to keep. Removing it while the arithmetic is still unfixed silently stops the truncate, which is the kind of tidy-up that looks safe in a review.

The fix at the assignment

<property name="BATCH_NUMBER"
          expression="substring-before(concat($ctx:BATCH_NUMBER + 1, '.'), '.')"
          scope="default" type="STRING"/>

concat forces XPath's own number-to-string conversion, and substring-before removes any .0 that survives. The expression gives the right answer whichever conversion path is taken, so it does not depend on Synapse internals. The same applies to every $ctx:X + Y in a property expression.

A related trap: the header is the signal, not the number

While fixing the float we also found that the delivery sequence set no truncate header at all. The target API's summary table rejected the insert because its truncate flag column was NULL. We initially attributed that to the float, and it was not: fixing only the arithmetic would not have cleared it. Every delivery sequence needs both halves:

<filter xpath="get-property('BATCH_NUMBER') = '1'">
    <then>
        <property name="Truncate" value="true" scope="transport" type="STRING"/>
    </then>
    <else>
        <property name="Truncate" value="false" scope="transport" type="STRING"/>
    </else>
</filter>

Doing without the Script mediator

A Script mediator would have produced an integer counter easily, and that is one of the few places where we still think one is justified. For most other jobs, native mediators do the work with less to go wrong. A script block is compiled when the sequence loads, and a single typographic quote pasted from a document is enough to stop it compiling. When that happens, the node logs the error and keeps running the previously deployed version, so the fix appears not to work when in fact it was never loaded.

The replacements we use:

  • Building strings: concat(...) in a property expression.
  • Removing characters: translate($ctx:x, '[]', '').
  • Taking the part after a separator: substring-after(...). For example, a bracketed table name like [Schema].[Table] becomes Table with substring-after(translate($ctx:tableName, '[]', ''), '.').
  • Trimming and collapsing whitespace: normalize-space(...).
  • Branching on a value: a switch with case regex, or a filter with source and regex. The default branch handles the empty value.
  • Testing for empty: string-length($ctx:x) = 0, never boolean($ctx:x), which is true for the string "false".

One caution about the toolbox. Synapse evaluates XPath with Jaxen, which is XPath 1.0. In one of our own products on WSO2 Micro Integrator 4.6, fn:replace(...) failed at runtime with "No Such Function replace" and the caller dropped into its fault sequence, while fn:upper-case, fn:substring, fn:concat and fn:string-length all worked, because Synapse registers those names itself. The 2.0-looking prefix is no guide to what is available; translate() is the 1.0 way to strip characters.

Where we still use a script:

  • Integer arithmetic on counters, where the XPath workaround above becomes unreadable.
  • Byte-level work, such as Base64 encoding for a Basic authentication header.
  • Slicing or reshaping JSON arrays.
  • Adding a line to a script block that already exists, rather than introducing a second one.

For bulk, repeating data there is a better tool than either. In the same product we replaced a script that walked a large XML response element by element with an XSLT stylesheet registered as a local entry. On that Micro Integrator build the xslt mediator required a key, so an inline stylesheet failed at deployment, and xsl:output method="text" threw a null pointer exception inside Axis2, while method="xml" worked. Both had to be found by testing, not assumed, but the result is declarative and has no script in it.

As an example, a fault sequence needed a log message that depended on which table and phase the failure occurred in. That is a switch on the short table name, built with the expressions above, with no script involved.

The same preference paid off on our own website. On 1 October 2026 we found that the Content Security Policy on pinuno.com.gh, which has allowed no inline scripts since August, had been silently blocking every inline onclick= analytics handler: a click raised a CSP violation in the browser console and sent nothing. We replaced them with declarative data-ga-event attributes read by one listener in the layout, and a test now fails on any inline handler that calls the tracker. Declarative markup, one interpreter, nothing for the policy to block.

Conclusion

The .0 defect is small, but it is a good example of a value that is correct for a person reading a log and wrong for the code that compares it. Fix it where the value is assigned, not where it is compared, and keep native mediators as the default so that a sequence's behaviour can be read directly from its configuration.

Our WSO2 course covers writing Synapse mediation with native mediators, taught by practitioners who do this work for a living.

K

Kofi Boateng

Related Articles

Article Info

Type
Article
Category
Enterprise Integration
Published
06 Oct 2026
Reading time
5 min

Search

Put this into practice

Our structured courses take these topics from article to hands-on skill, taught by the practitioners who write them.

Browse Courses