Notes on a simple NiFi custom processor project

Russell Bateman
August 2016
last update:

Table of Contents

Project filesystem layout
Maven (pom.xml) files
Project root pom.xml
Processor pom.xml
NAR pom.xml
Appendix 1: Links
Appendix 2: Source code
      src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
Appendix 3: A simplified project structure
Appendix 4: Names of JARs and NARs in project structure
Appendix 5: Definitive pom.xml set

Though I've been developing custom NiFi processors for the better part of a year now, I've never got around to making notes on the (rather confusing) set-up of a project in the filesystem and, especially, the pom.xml files needed to pull it off.

There are 3+ pom.xml files and your project necessarily has multiple modules as shown here. One is the "root" pom.xml, another is the pom.xml in the nar subdirectory, still another in the subdirectory (module) where resides the Java source code for your custom processor. (Here, I am only showing one subdirectory with code in it in this example. This is the processor subdirectory, but, if you intend spreading multiple custom processor code across multiple such subdirectories, you only need to them as siblings to processor as covered next.)

You can freely add more such subdirectories to this project—they each will have...

  1. a different name,
  2. processor here need not carry that specific name, but a more descriptive one
  3. the <modules> paragraph in the top-level pom.xml needs to name the additional modules you create,
  4. each submodule will have its own src subdirectory (just as processor here),
  5. each submodule will also have its own pom.xml with a <parent> paragraph referring to the root pom.xml,
  6. each submodule will also have its own src/main/resources/META-INF/services/org.apache.nifi.processor.Processor containing the names of the custom processor(s) coded under src. (I only have one here: Trick.)

The trick is to get the multiple pom.xml files set up. You can't do it with one because you must built each module into its own JAR, then roll that JAR into the NAR for deployment to NiFi.

I named my project "Trick" so there would be no mistake, confusion or ambiguity as to what is what here. The important source artifacts are:

  1. processor/src/main/.../Trick.java —code for the custom NiFi processor
  2. processor/src/test/.../TrickTest.java —code for testing Trick class
  3. processor/src/main/resources/.../org.apache.nifi.processor.Processor —resource that tells NiFi where your processor is
  4. processor/pom.xml —builds the processor JAR—you might have multiple such subdirectories (each with its own pom.xml)
  5. nar/pom.xml —rolls the processor JAR into the NAR
  6. pom.xml —the root pom.xml

Note that there is nothing sacred about the subdirectory names, nar, processor, nor the project directory name, trick.

Here's what the project looks like when finished, but before any build. (I've got some IntelliJ IDEA files in here—they don't count):

~/dev/trick $ tree
.
├── .idea
├── nar
│   ├── pom.xml
│   └── nar.iml
├── pom.xml
├── processor
│   ├── pom.xml
│   ├── processor.iml
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── com
│       │   │       └── etretatlogiciels
│       │   │           └── nifi
│       │   │               └── processor
│       │   │                   └── Trick.java
│       │   └── resources
│       │       └── META-INF
│       │           └── services
│       │               └── org.apache.nifi.processor.Processor
│       └── test
│           └── java
│               └── com
│                   └── etretatlogiciels
│                       └── nifi
│                           └── processor
│                               └── TrickTest.java
└── trick.iml

18 directories, 9 files

Or, if clearer, this illustration (which is missing the Java test file for simplicity's sake):

The Maven build is done this way, to produce the NAR for deployment (not showing the copious Maven output, but only what the project filesystem looks like afterward). Your mileage may vary, but it should look something very much like this. The NAR file is clearly highlighted: it is built from an intermediate JAR file, processor-1.0.SNAPSHOT.jar. If you have multiple subdirectories (like processor here), each with code to build custom NiFi processors, the NAR will be built from JAR files underneath each of those subdirectories.

Note that some treatments of this topic give longer, more descriptive names to module/subdirectory names like nifi-processor-bundle, what I call simply parent here inside pom.xml, nifi-myprocessor-processor, what I call processor here, and nifi-myprocessor-nar, what I call nar. (Replace myprocessor with meaningful name that echo what you're doing, what you call your custom processor, etc.).

~/dev/trick $ mvn clean test package
.
.
.
~/dev/trick $ tree
.
├── .idea
├── nar
│   ├── pom.xml
│   ├── target
│   │   ├── classes
│   │   │   └── META-INF
│   │   │       └── nar
│   │   │           └── com.etretatlogiciels.nifi.processor
│   │   │               └── nar
│   │   │                   └── nar.properties
│   │   ├── maven-archiver
│   │   │   └── pom.properties
│   │   └── trick-nar-1.0-SNAPSHOT.nar
│   └── nar.iml
├── pom.xml
├── processor
│   ├── pom.xml
│   ├── processor.iml
│   ├── src
│   │   ├── main
│   │   │   ├── java
│   │   │   │   └── com
│   │   │   │       └── etretatlogiciels
│   │   │   │           └── nifi
│   │   │   │               └── processor
│   │   │   │                   └── Trick.java
│   │   │   └── resources
│   │   │       └── META-INF
│   │   │           └── services
│   │   │               └── org.apache.nifi.processor.Processor
│   │   └── test
│   │       └── java
│   │           └── com
│   │               └── etretatlogiciels
│   │                   └── nifi
│   │                       └── processor
│   │                           └── TrickTest.java
│   └── target
│       ├── classes
│       │   ├── com
│       │   │   └── etretatlogiciels
│       │   │       └── nifi
│       │   │           └── processor
│       │   │               └── Trick.class
│       │   └── META-INF
│       │       └── services
│       │           └── org.apache.nifi.processor.Processor
│       ├── generated-sources
│       │   └── annotations
│       ├── generated-test-sources
│       │   └── test-annotations
│       ├── maven-archiver
│       │   └── pom.properties
│       ├── maven-status
│       │   └── maven-compiler-plugin
│       │       ├── compile
│       │       │   └── default-compile
│       │       │       ├── createdFiles.lst
│       │       │       └── inputFiles.lst
│       │       └── testCompile
│       │           └── default-testCompile
│       │               ├── createdFiles.lst
│       │               └── inputFiles.lst
│       ├── processor-1.0-SNAPSHOT.jar
│       ├── surefire-reports
│       │   ├── com.etretatlogiciels.nifi.processor.TrickTest.txt
│       │   └── TEST-com.etretatlogiciels.nifi.processor.TrickTest.xml
│       └── test-classes
│           └── com
│               └── etretatlogiciels
│                   └── nifi
│                       └── processor
│                           └── TrickTest.class
└── trick.iml

52 directories, 23 files

Maven files

Here are the source files listed earlier. This processor is pretty much nonsense and doesn't do anything, and its test doesn't test anything. However, this exposé is only to help you get a) the pom.xml files and b) the layout of the project and important files.

The project's pom.xml files, for the project root, the source code subdirectory and the NAR-builder. Now, what's highlighted is very important for making the build work.

Project root pom.xml:

Mostly, this defines the modules to build the source code and bundle the NAR. It also holds the plug-in that Maven calls to build the NAR. trick is an ugly name for an artifact, but this is the parent that builds the NAR—this ugly name will only be seen in Maven, never on JAR artifacts or the NAR.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                  http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <!-- root pom.xml -->
  <groupId>com.etretatlogiciels.nifi.processor</groupId>
  <artifactId>trick</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>
  <description>Etretat Logiciels NiFi Processor</description>

  <properties>
    <nifi.version>1.9.2</nifi.version>
    <slf4j.version>1.7.21</slf4j.version>
    <junit.version>4.12</junit.version>
    <maven.compiler.plugin.version>3.5.1</maven.compiler.plugin.version>
    <nifi-nar-maven-plugin.version>1.3.1</nifi-nar-maven-plugin.version>
    <maven.install.skip>true</maven.install.skip>
    <maven.deploy.skip>true</maven.deploy.skip>
  </properties>

  <modules>
    <module>processor</module> <!-- as noted: you might have multiple such modules -->
    <module>nar</module>
  </modules>
</project>
Processor subdirectory's pom.xml:

Notice that we're going to build trick-1.0.SNAPSHOT.jar. This will be supplied to the NAR file build later.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                  http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <!-- processor/pom.xml -->
  <artifactId>processor</artifactId>
  <packaging>jar</packaging>

  <parent>
    <groupId>com.etretatlogiciels.nifi.processor</groupId>
    <artifactId>trick</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../pom.xml</relativePath>
  </parent>

  <dependencies>
    <dependency>
      <groupId>org.apache.nifi</groupId>
      <artifactId>nifi-api</artifactId>
      <version>${nifi.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.nifi</groupId>
      <artifactId>nifi-processor-utils</artifactId>
      <version>${nifi.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.nifi</groupId>
      <artifactId>nifi-mock</artifactId>
      <version>${nifi.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>${slf4j.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven.compiler.plugin.version}</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
NAR bundle subdirectory's pom.xml:

Notice that we're going to build trick-nar-1.0.SNAPSHOT.nar. In order to do that, it's this pom.xml whose artifact identity must be "trick." The dependency is the JAR full of processor code built in the processor subdirectory.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                  http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <!-- nar/pom.xml -->
  <artifactId>nar</artifactId>
  <packaging>nar</packaging>

  <parent>
    <groupId>com.etretatlogiciels.nifi.processor</groupId>
    <artifactId>trick</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../pom.xml</relativePath>
  </parent>

  <dependencies>
    <dependency>
      <groupId>com.etretatlogiciels.nifi.processor</groupId>
      <artifactId>processor</artifactId>
      <version>1.0-SNAPSHOT</version>
      <type>jar</type>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <!-- what builds the NAR file... -->
        <groupId>org.apache.nifi</groupId>
        <artifactId>nifi-nar-maven-plugin</artifactId>
        <version>${nifi-nar-maven-plugin.version}</version>
        <extensions>true</extensions>
      </plugin>
    </plugins>
  </build>
</project>

One last note. When ready to use the new processor, you have only to drop the .nar file into ${NIFI_ROOT}/lib, then restart NiFi.

After building, you should be able to see something like this, proving that the JAR (processor) and NAR (NiFi deployment artifact) were built:

russ@nargothrond ~/dev/trick $ find . -name '*.jar'
./nar/target/classes/META-INF/bundled-dependencies/nifi-api-1.9.2.jar
./nar/target/classes/META-INF/bundled-dependencies/jBCrypt-0.4.1.jar
./nar/target/classes/META-INF/bundled-dependencies/processor-1.0-SNAPSHOT.jar
./nar/target/classes/META-INF/bundled-dependencies/nifi-properties-1.9.2.jar
./nar/target/classes/META-INF/bundled-dependencies/jackson-core-2.9.7.jar
./nar/target/classes/META-INF/bundled-dependencies/jackson-annotations-2.9.0.jar
./nar/target/classes/META-INF/bundled-dependencies/jackson-databind-2.9.7.jar
./nar/target/classes/META-INF/bundled-dependencies/nifi-security-utils-1.9.2.jar
./nar/target/classes/META-INF/bundled-dependencies/nifi-processor-utils-1.9.2.jar
./nar/target/classes/META-INF/bundled-dependencies/bcprov-jdk15on-1.60.jar
./nar/target/classes/META-INF/bundled-dependencies/commons-lang3-3.8.1.jar
./nar/target/classes/META-INF/bundled-dependencies/nifi-utils-1.9.2.jar
./nar/target/classes/META-INF/bundled-dependencies/bcpkix-jdk15on-1.60.jar
./nar/target/classes/META-INF/bundled-dependencies/commons-io-2.6.jar
./nar/target/classes/META-INF/bundled-dependencies/commons-codec-1.11.jar
./processor/target/processor-1.0-SNAPSHOT.jar
russ@nargothrond ~/dev/trick $ find . -name '*.nar'
./nar/target/trick-nar-1.0-SNAPSHOT.nar

Appendix 1: Links

Appendix 2: Source code

TrickTest.java:
package com.etretatlogiciels.nifi.processor;

import java.util.List;

import org.junit.Test;
import static org.junit.Assert.assertEquals;

import org.apache.nifi.util.MockFlowFile;
import org.apache.nifi.util.TestRunner;
import org.apache.nifi.util.TestRunners;

public class TrickTest
{
  private final static String TEST_CONTENT = "This is a test.";

  @Test
  public void testProcessor()
  {
    TestRunner runner = TestRunners.newTestRunner( Trick.class );

    runner.setProperty( Trick.TRICK_PROPERTY, "This is just a property value." ); // will fail to validate without property
    runner.enqueue( TEST_CONTENT );                                               // injects flowfile content for testing
    runner.run( 1 );                                                              // initializes processor, calls onTrigger()
    runner.assertQueueEmpty();

    List< MockFlowFile > flowfiles = runner.getFlowFilesForRelationship( Trick.RELATIONSHIP );
    MockFlowFile         flowfile  = results.get( 0 );
    String               content   = new String( runner.getContentAsByteArray( flowfile ) );

    assertEquals( "Changed flowfile content?", content, TEST_CONTENT );
  }
}
Trick.java:

(What does this processor do? It merely transfers the in-coming flowfile straight through, unchanged, to the success relationship.)

package com.etretatlogiciels.nifi.processor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.processor.AbstractProcessor;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.ProcessorInitializationContext;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;

@Tags( { "trick" } )
@CapabilityDescription( "Trick processor." )
public class Trick extends AbstractProcessor
{
  @Override
  public void onTrigger( final ProcessContext context, final ProcessSession session )
        throws ProcessException
  {
    FlowFile flowfile = session.get();

    if( flowfile == null )
    {
      context.yield();
      return;
    }

    session.transfer( flowfile, TRICK_RELATIONSHIP );
  }

  public static final PropertyDescriptor TRICK_PROPERTY = new PropertyDescriptor.Builder()
            .name( "Trick Property" )
            .description( "Just a property." )
            .required( true )
            .addValidator( StandardValidators.NON_EMPTY_VALIDATOR )
            .build();

  public static final Relationship TRICK_RELATIONSHIP = new Relationship.Builder()
            .name( "Trick relationship" )
            .description( "Just a relationship." )
            .build();

  private List< PropertyDescriptor > properties;
  private Set< Relationship >        relationships;

  @Override
  protected void init( final ProcessorInitializationContext context )
  {
    final List< PropertyDescriptor > props = new ArrayList<>();
    props.add( TRICK_PROPERTY );
    this.properties = Collections.unmodifiableList( props );

    final Set< Relationship > relationships = new HashSet<>();
    relationships.add( TRICK_RELATIONSHIP );
    this.relationships = Collections.unmodifiableSet( relationships );
  }

  @Override
  public Set< Relationship > getRelationships()
  {
    return this.relationships;
  }

  @Override
  public final List< PropertyDescriptor > getSupportedPropertyDescriptors()
  {
    return properties;
  }
}
src/main/resources/META-INF/services/org.apache.nifi.processor.Processor:
com.etretatlogiciels.nifi.processor.Trick

Appendix 3: A simplified project structure

It's a temptation to set up a project with simpler structure with only one module in IntelliJ IDEA (or a single Eclipse project) and I'll show a little of it here. Do not succumb to the temptation! What results will run and work, but only partially. Among things that do not work are that, when getting usage for the processor (in the UI canvas), you will not get what you put into the @CapabilityDescription annotation on your processor code. Instead, you get some generic or default documentation entitled Apache NiFi Overview and no discussion of your processor. There may be other aspects that do not work; what I report here is my own experience.

The "three-module" approach (covered above) is held by the Apache NiFi community as the minimum required for successful and complete custom processor building and deployment.

The following notes used to figure in these greater notes as an alternative until I discovered that it won't work. I leave it here for comparison to your own in case you stumble upon these notes because you've done it this way and you're in search of why it's not working.

Such structure uses a single pom.xml that contains everything to build trick-nar-1.0-SNAPSHOT.nar. The source code referred to is the same as above.

~/dev/trick $ tree
.
├── .idea
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── etretatlogiciels
│   │   │           └── nifi
│   │   │               └── processor
│   │   │                   └── Trick.java
│   │   └── resources
│   │       └── META-INF
│   │           └── services
│   │               └── org.apache.nifi.processor.Processor
│   └── test
│       └── java
│           └── com
│               └── etretatlogiciels
│                   └── nifi
│                       └── processor
│                           └── TrickTest.java
├── target
│   ├── classes
│   ├── etc...
│   └── trick-nar-1.0-SNAPSHOT.nar
└── trick.iml

Source code and pom.xml

Here's the single pom.xml file at project's root. What keeps the documentation from working is probably that a NAR must be built from a (one or more) JAR and the full and complete bundling process does not occur.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.etretatlogiciels.nifi.processor</groupId>
  <artifactId>trick</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>nar</packaging>
  <description>Etretat Logiciels NiFi Processor</description>

  <properties>
    <nifi.version>1.9.2</nifi.version>
    <slf4j.version>1.7.21</slf4j.version>
    <junit.version>4.12</junit.version>
    <maven.compiler.plugin.version>3.5.1</maven.compiler.plugin.version>
    <nifi-nar-maven-plugin.version>1.3.1</nifi-nar-maven-plugin.version>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.install.skip>true</maven.install.skip>
    <maven.deploy.skip>true</maven.deploy.skip>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.nifi</groupId>
      <artifactId>nifi-api</artifactId>
      <version>${nifi.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.nifi</groupId>
      <artifactId>nifi-processor-utils</artifactId>
      <version>${nifi.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.nifi</groupId>
      <artifactId>nifi-mock</artifactId>
      <version>${nifi.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>${slf4j.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.nifi</groupId>
        <artifactId>nifi-nar-maven-plugin</artifactId>
        <version>${nifi-nar-maven-plugin.version}</version>
        <extensions>true</extensions>
      </plugin>
    </plugins>
  </build>
</project>

Appendix 4: Names of JARs and NARs in project structure

First, we don't care especially how the name of the subordinate JAR(s) end up, here, processor-1.0.SNAPSHOT.jar because it's only the NAR that counts. (Although, you will see the JAR name(s) if you dig down into the NAR archive.)

But, the name of the resulting NAR above is awkward. It's formed from the NAR subdirectory pom.xml's artifactId:

${project.artifactId}-${project.version}.nar

Hence, it's not possible to name the artifactId the same as the parent's in nar subdirectory's pom.xml in order to get trick-1.0.SNAPSHOT.nar instead of trick-nar-1.0.SNAPSHOT.nar.

However, nifi-nar-maven-plugin gives us a work-around. Just add this to the plug-in configuration:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.nifi</groupId>
      <artifactId>nifi-nar-maven-plugin</artifactId>
      <version>${nifi-nar-maven-plugin.version}</version>
      <extensions>true</extensions>
      <configuration>
        <narName>${project.artifactId}-${project.version}</narName>
      </configuration>
    </plugin>
  </plugins>
</build>

This works a charm and our distributable NAR receives the name trick-1.0-SNAPSHOT.nar.

Appendix 5: My definitive small-NAR pom.xml set...

The root pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.windofkeltia.processor</groupId>
  <artifactId>Claims</artifactId>
  <version>1.0</version>
  <packaging>pom</packaging>
  <description>X12 Claims Processor</description>

  <properties>
    <nifi.version>1.13.2</nifi.version>
    <yarsquidy.version>2.0.0</yarsquidy.version>
    <slf4j.version>1.7.21</slf4j.version>
    <junit.version>4.13.2</junit.version>
    <maven-antrun-plugin.version>3.0.0</maven-antrun-plugin.version>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.compiler.release>8</maven.compiler.release>
    <maven.compiler.plugin.version>3.8.1</maven.compiler.plugin.version>
    <maven-surefire-plugin.version>2.21.0</maven-surefire-plugin.version>
    <nifi-nar-maven-plugin.version>1.4.0</nifi-nar-maven-plugin.version>
    <maven.install.skip>true</maven.install.skip>
    <maven.deploy.skip>true</maven.deploy.skip>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <modules>
    <module>processor</module>
    <module>nar</module>
  </modules>
</project>
The NAR pom.xml:

This is one whose artifactId names the NAR file produced.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <artifactId>x12-claims</artifactId>
  <packaging>nar</packaging>

  <parent>
    <groupId>com.windofkeltia.processor</groupId>
    <artifactId>Claims</artifactId>
    <version>1.0</version>
    <relativePath>../pom.xml</relativePath>
  </parent>

  <dependencies>
    <dependency>
      <groupId>com.windofkeltia.processor</groupId>
      <artifactId>processor</artifactId>
      <version>1.0</version>
      <type>jar</type>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.nifi</groupId>
        <artifactId>nifi-nar-maven-plugin</artifactId>
        <version>${nifi-nar-maven-plugin.version}</version>
        <extensions>true</extensions>
        <configuration>
          <narName>${project.artifactId}-${project.version}</narName>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
The processor pom.xml:

Of course, in a big NAR project, you'd have multiple subdirectories like this and they'd all be referenced in the root pom.xml's <modules ... > list.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <artifactId>processor</artifactId>
  <packaging>jar</packaging>

  <parent>
    <groupId>com.windofkeltia.processor</groupId>
    <artifactId>Claims</artifactId>
    <version>1.0</version>
    <relativePath>../pom.xml</relativePath>
  </parent>

  <dependencies>
    <dependency>
      <groupId>org.apache.nifi</groupId>
      <artifactId>nifi-api</artifactId>
      <version>${nifi.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.nifi</groupId>
      <artifactId>nifi-processor-utils</artifactId>
      <version>${nifi.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.nifi</groupId>
      <artifactId>nifi-mock</artifactId>
      <version>${nifi.version}</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>com.yarsquidy</groupId>
      <artifactId>x12-parser</artifactId>
      <version>${yarsquidy.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>${slf4j.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven.compiler.plugin.version}</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>${maven-antrun-plugin.version}</version>
        <executions>
          <execution>
            <phase>compile</phase>
            <configuration>
              <target>
                <!-- How to echo out properties and their definitions; uncomment this!
                <echoproperties /> -->
              </target>
            </configuration>
            <goals>
              <goal>run</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>${maven-surefire-plugin.version}</version>
        <configuration>
          <systemPropertyVariables>
            <catalina.home>${project.build.directory}</catalina.home>
            <buildDirectory>${project.build.directory}</buildDirectory>
          </systemPropertyVariables>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Upon failure...

If you cannot restart the UI successfully after adding your NAR to the extensions subdirectory, checkout what's in logs/nifi-app.log If you get an error such as this one:

Failure to launch NiFi due to java.util.ServiceConfigurationError: \
    org.apache.nifi.processor.Processor: Provider could not be instantiated

It could mean that you must add the following dependency to pom.xml in the nar subdirectory (where ${nifi.version} is the version of NiFi you intend to support):

<dependency>
  <groupId>org.apache.nifi</groupId>
  <artifactId>nifi-standard-nar</artifactId>
  <version>${nifi.version}</version>
  <type>nar</type>
</dependency>