Write a Macro in Java

Last modified by Eleni Cojocariu on 2026/10/13 12:59

Tutorial

This tutorial builds simple-example, a macro that displays the value it is called with, from the Maven command that generates the module to the macro running in a wiki page. Rendering Macros explains what a macro is made of and how the rendering engine runs it; this page writes one.

You need a Java version matching the XWiki runtime needs and Maven, and it helps to know the XWiki Rendering architecture the macro runs inside.

  1. Generate the module from the XWiki Rendering macro archetype:
    mvn archetype:generate \
      -DarchetypeGroupId=org.xwiki.rendering \
      -DarchetypeArtifactId=xwiki-rendering-archetype-macro

    Maven asks which version of the archetype to use, and the newest is the default: the archetype is released with every XWiki version. It then asks for the groupId, artifactId, version and package of the module; this tutorial answers com.acme, example, 1.0-SNAPSHOT and com.acme.

  2. Look at what the archetype generated:
    example/
      pom.xml
      src/main/java/com/acme/ExampleMacroParameters.java
      src/main/java/com/acme/internal/ExampleMacro.java
      src/main/resources/META-INF/components.txt
      src/test/java/com/acme/IntegrationTests.java
      src/test/resources/example1.test
      src/test/resources/example2.test

    components.txt holds one full class name per line, com.acme.internal.ExampleMacro here, and that line is what registers the macro.

  3. Read ExampleMacro.java, the macro itself:
    package com.acme.internal;
    
    import javax.inject.Named;
    
    import java.util.List;
    
    import org.xwiki.component.annotation.Component;
    import org.xwiki.rendering.block.Block;
    import org.xwiki.rendering.block.WordBlock;
    import org.xwiki.rendering.block.ParagraphBlock;
    import org.xwiki.rendering.macro.AbstractMacro;
    import org.xwiki.rendering.macro.MacroExecutionException;
    import com.acme.ExampleMacroParameters;
    import org.xwiki.rendering.transformation.MacroTransformationContext;
    
    /**
     * Example Macro.
     */
    @Component
    @Named("simple-example")
    public class ExampleMacro extends AbstractMacro<ExampleMacroParameters>
    {
        /**
         * The description of the macro.
         */
        private static final String DESCRIPTION = "Simple Example Macro";
    
        /**
         * Create and initialize the descriptor of the macro.
         */
        public ExampleMacro()
        {
            super("Simple Example", DESCRIPTION, ExampleMacroParameters.class);
        }
    
        @Override
        public List<Block> execute(ExampleMacroParameters parameters, String content, MacroTransformationContext context)
            throws MacroExecutionException
        {
            List<Block> result;
    
            List<Block> wordBlockAsList = List.of(new WordBlock(parameters.getParameter()));
    
            // Handle both inline mode and standalone mode.
            if (context.isInline()) {
                result = wordBlockAsList;
            } else {
                // Wrap the result in a Paragraph Block since a WordBlock is an inline element, and it needs to be
                // inside a standalone block.
                result = List.of(new ParagraphBlock(wordBlockAsList));
            }
    
            return result;
        }
    
        @Override
        public boolean supportsInlineMode()
        {
            return true;
        }
    }

    AbstractMacro leaves two methods to implement. execute builds the blocks the macro call is replaced by. supportsInlineMode says whether the macro may be used inside existing content rather than on its own, as in a sentence or in a heading:

    = hello {{velocity}}world{{/velocity}} =

    That is also why the result is built twice: called inline, the macro returns its WordBlock as it is, and called on its own it wraps it in a ParagraphBlock, because a word is an inline element and cannot stand alone in the page.

  4. Read ExampleMacroParameters.java, which declares what the macro can be called with:
    package com.acme;
    
    import org.xwiki.properties.annotation.PropertyMandatory;
    import org.xwiki.properties.annotation.PropertyDescription;
    
    /**
     * Parameters for the {@link com.acme.internal.ExampleMacro} Macro.
     */
    public class ExampleMacroParameters
    {
        /**
         * @see {@link #getParameter()}
         */
        private String parameter;
    
        /**
         * @return the example parameter
         */
        public String getParameter()
        {
            return this.parameter;
        }
    
        /**
         * @param parameter the example parameter
         */
        @PropertyMandatory
        @PropertyDescription("Simple Example parameter")
        public void setParameter(String parameter)
        {
            this.parameter = parameter;
        }
    }

    Rendering Macros covers what this bean declares and how the editors read it; the Properties Module documents the annotations this example does not use.

  5. Read example1.test. The rendering system has a test framework that makes a macro easy to test, from a declarative file like this one, which checks the macro called on its own:
    .runTransformations
    .#-----------------------------------------------------
    .input|xwiki/2.1
    .# Test the macro in standalone mode
    .#-----------------------------------------------------
    {{simple-example parameter="hello"/}}
    .#-----------------------------------------------------
    .expect|xhtml/1.0
    .#-----------------------------------------------------
    <p>hello</p>
    .#-----------------------------------------------------
    .expect|event/1.0
    .#-----------------------------------------------------
    beginDocument
    beginMacroMarkerStandalone [simple-example] [parameter=hello]
    beginParagraph
    onWord [hello]
    endParagraph
    endMacroMarkerStandalone [simple-example] [parameter=hello]
    endDocument

    The framework parses the input, here XWiki Syntax 2.1, and compares the result against every expect block the file declares: XHTML 1.0, and the events the rendering system produces. Those events are its pivot format, the one every renderer consumes, so they state the result more precisely than any output syntax does. .runTransformations is what makes the macro transformation run on the parsed content, and so what makes the macro execute at all.

  6. Read example2.test, the same macro called inside a sentence:
    .runTransformations
    .#-----------------------------------------------------
    .input|xwiki/2.1
    .# Test the macro in inline mode
    .#-----------------------------------------------------
    This is inline {{simple-example parameter="hello"/}}
    .#-----------------------------------------------------
    .expect|xhtml/1.0
    .#-----------------------------------------------------
    <p>This is inline hello</p>
    .#-----------------------------------------------------
    .expect|event/1.0
    .#-----------------------------------------------------
    beginDocument
    beginParagraph
    onWord [This]
    onSpace
    onWord [is]
    onSpace
    onWord [inline]
    onSpace
    beginMacroMarkerInline [simple-example] [parameter=hello]
    onWord [hello]
    endMacroMarkerInline [simple-example] [parameter=hello]
    endParagraph
    endDocument

    The macro is called inside a paragraph this time, so the marker is beginMacroMarkerInline and the word it returns joins the sentence instead of starting a paragraph of its own.

  7. Build the module and run both tests:
    mvn install

    IntegrationTests.java is what runs them. It executes every *.test file found on the classpath, so a new test file needs no new Java:

    package com.acme;
    
    import org.xwiki.rendering.test.integration.junit5.RenderingTest;
    import org.xwiki.test.annotation.AllComponents;
    
    /**
     * Run all tests found in {@code *.test} files located in the classpath. These {@code *.test} files must follow the
     * conventions described in {@link org.xwiki.rendering.test.integration.TestDataParser}.
     */
    @AllComponents
    class IntegrationTests extends RenderingTest
    {
    }
  8. Deploy the built jar. A macro is an XWiki Component, and is deployed like any other component, as the "Deploying the Component" section of Writing Components describes.
  9. Call the macro from the content of a wiki page, on its own and inside a sentence:
    {{simple-example parameter="hello"/}}
    
    This is inline {{simple-example parameter="hello"/}}
  10. View that page. Each call is replaced by the value it was given, as a paragraph the first time and as a word in the sentence the second time:
    hello
    
    This is inline hello

FAQ

How do I translate the name, description and parameters of the macro?

Ship a translation resource in the module, using the same keys as a wiki macro: Translate a Wiki Macro lists them.

Related

Get Connected