When a shared connection pool or client belongs in a NiFi controller service
A controller service is the NiFi extension point for resources that many processors should share, such as a database connection pool, an API client or a cache. If your processors each open their own connections to the same external system, the case for a controller service is usually strongest there, and this page covers how to decide whether you need to build one and what the build involves. In the NiFi Developer's Guide, controller services sit in their own module family, separate from processors, so a service can be packaged once and referenced by any number of processors in the flow. Before you write one, check the bundled services and the community extensions for your target system, because a supported service that already does the job is cheaper to operate than custom code. Apache NiFi is licensed under Apache-2.0 and the project offers no paid edition, so the cost of a custom service is the engineering time to design, test and maintain it.
The lifecycle is the first thing to plan for. NiFi enables a controller service when an operator starts it, validates its properties, and then keeps it running for every processor that references it. Disabling a service stops those processors from using it, and the lifecycle annotations on your methods decide what runs at enable time and at disable time. Build the connection or client in the enable path, release it in the disable path, and keep per-request work out of the service itself. Expect the service to be reconfigured while a flow is running, so plan how an in-flight connection behaves when a property changes. Confirm the exact annotation names and their ordering against the version of NiFi you target, since the Developer's Guide is the authoritative reference for that behavior.
The decision to write a custom service rather than reuse an existing one usually comes down to three questions: whether the external system has a client library your team can wrap, whether the sharing model you need matches what existing services expose, and whether you need to control how credentials and connection state are handled. If the answer to all three is that an existing service fits, reuse it. If one processor's private client is leaking connections or duplicating sockets across dozens of processors, a shared service is the right fix. Ask your NiFi developers to document which processors will depend on the service, what happens to them when it is disabled, and which settings operators must change in production, because those answers shape the API you publish in the first place.
What a custom controller service does in NiFi
A controller service holds a shared resource, such as a connection pool, client or cache, so that several processors can use one instance instead of each building its own. The pieces below are the ones the NiFi Developer's Guide describes, and they fit together as one contract between the service and the processors that depend on it.
Controller service API
The API is the interface that processors compile against. It declares the methods a processor may call, such as getting a connection or looking up a value, and it carries no implementation details. Keeping it in its own NAR lets processors depend on the contract without pulling in the driver or client library behind it. Confirm the current interface and its NAR layout against the developer documentation for your NiFi version, since packaging guidance has changed across releases.
Controller service implementation
The implementation provides the API interface and owns the real resource, including the client, pool or cache and its credentials. It is packaged in a separate NAR from the API so that its dependencies stay isolated. Each service instance is configured on the canvas, and the processors that use it never see the implementation class directly. Check how the implementation's dependencies are loaded in your build, because conflicting libraries are a common source of failure.
Property descriptors
Property descriptors define the settings an operator sees when configuring the service, such as host, port, timeouts and secret values. Each descriptor sets whether a value is required, its allowable values and whether it is sensitive. Sensitive descriptors keep credentials out of plain view in the UI and flow definitions. Review each descriptor's sensitivity and default values with your security team before the service is shared across flows.
Validation before enabling
Validation checks the configured properties before the service can run. NiFi reports invalid or missing settings so an operator can correct them before enabling the service, rather than discovering the problem when a processor first fails. Custom validation can check combinations of properties, such as a timeout that must not exceed another value. Test invalid combinations in your unit tests, and confirm how each NiFi version surfaces validation results in the UI.
Lifecycle annotations for enable and disable
Lifecycle annotations mark the methods NiFi calls when a service is enabled and disabled. The enable method opens the pool or client, and the disable method releases those resources so that a stopped flow does not leave connections open. Teams should confirm the annotation names and their call order against the current developer documentation, then test that disabling releases every resource the service acquired.
How processors reference a service
A processor declares a property whose type is the service API, and the operator selects an enabled service instance from a list. Many processors can point at one instance, which is how a shared pool or cache is used across a flow. Before sharing a service, confirm which processors depend on it, since disabling or changing it affects every consumer. Confirm the reference mechanics for your NiFi version in the developer documentation.
Which resource belongs in a controller service and which stays in the processor
A connection pool or API client is the most common case for a controller service. Several processors often need to reach the same database, message broker or REST endpoint, and each one opening its own connections multiplies the load on the target system and makes credential changes painful. Placing the client in a controller service lets every processor that references it share one configured connection pool, and lets operators change the endpoint or credentials in one place. The trade-off is shared failure impact: if the service is misconfigured, disabled or the remote system is down, every referencing processor is affected at once. Processors that rely on it should be designed to route failures to a retry or failure relationship instead of stalling the flow.
- Many consumers: Two or more processors that talk to the same system are the clearest signal for a shared service.
- Centralized credentials: Sensitive values live in one service rather than in each processor's properties.
- Duplicated connections: Per-processor clients multiply open connections and make rate limits harder to honor.
- Shared failure impact: One outage or bad setting stops every processor that references the service.
- Lifecycle control: Enabling, disabling and restarting the service happens at a single point.
Before building a new client, check whether an existing controller service already covers the target system, and confirm in the component documentation whether its pool size and timeouts suit your load.
A cache that several flows or processors read and write belongs in a controller service, because its value comes from being shared. A lookup table used for enrichment, or a deduplication store, only works across processors if they all reference the same backing store. Keeping the cache in a service also means its storage backend can change without editing every flow. The cost is coupling: flows that share a cache depend on its contents, its eviction behavior and its availability, so a change made for one flow can affect another. Cache data should be treated as a dependency with an owner, not as a side effect of one processor.
- Shared lookups: Enrichment data read by several flows is held once and reused.
- Deduplication across flows: A common store prevents duplicates that separate caches would miss.
- Backend choice: The service can hide whether the cache is in memory, on disk or external.
- Coupled behavior: Eviction, size limits and outages affect every flow that uses the cache.
- Ownership: Name one team responsible for the cache's schema, retention and access.
Confirm in the NiFi 2 component documentation how the distributed map cache components are named in your version, since they were renamed in the 2.0 migration.
A resource that only one processor needs, and that carries state tied to that processor's work, usually stays in the processor. Examples include a file handle being written in sequence, a counter that tracks position in a stream, or an in-memory buffer that is flushed on a schedule. Moving such state into a controller service would make a single processor depend on a shared object whose lifecycle it does not control, and it would make concurrent tasks harder to reason about. Processor-level state is also easier to test, because the test harness can set it up without registering additional services. The trade-off is that the state is not visible to other processors and does not survive a component being recreated, so it must not hold anything the flow needs to recover from.
- Single owner: Only the processor that uses the resource needs to see its state.
- Concurrency: Keeping state local avoids coordinating access across processors.
- Simpler tests: The unit test can supply the state without extra service setup.
- No recovery guarantee: Local state is lost if the component is recreated, so it should not be the system of record.
- Scheduling interaction: Confirm how concurrent tasks and run schedules affect any mutable field.
If the state must survive restarts or be shared across nodes, that is a sign to move it into a service or an external store and to review how it is stored in the cluster.
A transformation that applies once, to one flow, with no external resource behind it belongs inside a processor or in an existing record-processing component. Creating a controller service for a small mapping or parsing step adds configuration, a separate lifecycle and another component to document, with no sharing benefit. Processors that transform content can take their parameters from their own properties and remain self-contained in the flow definition. The trade-off is reuse: if the same transformation later appears in several flows, the logic should move into a shared component so it is maintained once. Until that happens, the simpler placement keeps the flow easier to read and version.
- No sharing benefit: A service only pays off when more than one component needs the same object.
- Less configuration: Properties stay on the processor where a reader can see them.
- Easier review: The transformation is visible in the flow without following a service reference.
- Reuse threshold: Move logic into a shared component once the same rule appears in several flows.
- Existing components first: Check whether a record processor or expression already covers the case.
Before writing custom transformation code, check the component documentation for an existing processor that performs the step, and confirm the choice against the restricted component policies in your environment.
Before writing a controller service, check whether NiFi or a bundle you already use provides one for the target system. An existing service has been tested in many flows, is documented in the component reference and is maintained with the rest of the platform. A custom service is justified when no existing component supports the protocol, authentication method or internal API you need, or when the existing one lacks a required behavior and extending it is not possible. Custom services add maintenance work at every NiFi upgrade, because their API and implementation must track changes in the framework. Both paths require a decision on who owns the code after delivery.
- Coverage check: Search the NiFi component documentation for a service that already speaks the target protocol.
- Upgrade exposure: Custom services must be rebuilt and retested when NiFi changes.
- Behavior gaps: Confirm whether the gap can be closed with configuration before writing code.
- Ownership: Decide who fixes the service after go-live and who reviews changes.
- Source access: Review the source repository in the apache/nifi project to understand how existing services are structured.
Ask the delivery team to show which existing services were evaluated and why each was rejected, so the decision can be reviewed later.
How the build sequence runs from API design to shared use
The sequence moves a controller service from an agreed interface to a tested, deployable NAR pair and then to one configured service that several processors share. Each step ends with a check that the next step depends on.
Agree the service API before any code
The team writes down what the service must do for its callers: the operations processors will call, the data types they exchange, and the errors callers must handle. A developer and the flow owner review this together, and it stays free of implementation detail such as which client library is used. The output is a short interface contract, agreed in writing, that names each method and its expected behavior. The step is done when the processor authors confirm they can build against that contract without asking for changes.
Create the API and implementation NAR pair
The developer creates two Maven modules: an API NAR that holds only the service interface, and an implementation NAR that depends on the API NAR and contains the class that does the work. The split lets processors compile against the interface while the implementation can change or be replaced without touching them. The implementation module declares the API NAR as its dependency so NiFi can resolve the service at runtime. The output is two buildable modules with a clean dependency direction, confirmed by a build that produces both NAR files.
Add property descriptors and validation rules
The developer defines a property descriptor for each setting the service needs, such as endpoint, credentials reference, pool size or timeout, and marks which values are sensitive. Validators reject bad input when the flow is configured, so errors appear in the UI before the service starts. Required properties and dependencies between properties are declared here, not discovered at runtime. The step is complete when an invalid configuration is refused in the NiFi UI and a valid one is accepted, which the developer checks on the development instance.
Implement enable and disable lifecycle methods
The developer marks one method with the enable annotation to open connections or build the pool, and one with the disable annotation to close them cleanly. Enable runs when the service is turned on, so it must fail visibly if a dependency is unreachable rather than hiding the error. Disable must release every resource so that a restart or configuration change does not leak connections. The output is a service that can be enabled, disabled and re-enabled repeatedly on the development instance without leaving open resources, which the developer confirms by watching the connection count.
Write unit tests for the service and its contract
The developer tests the implementation directly and through a mock or stub for the external system, covering the normal path, error responses and the lifecycle transitions. Tests also check that validators accept and reject the expected values. The tests run in the build so a broken contract fails before packaging. The step is done when the test suite passes locally and in the team's CI pipeline, with coverage of every operation named in the agreed contract.
Package and deploy the NAR pair to development
The developer builds both NAR files, copies them into the development NiFi extensions directory, and restarts the instance so the classloader picks them up. The team confirms that the new service type appears in the controller service list and that the API NAR and implementation NAR are both loaded without errors in the logs. The output is a running development instance where the service can be created, configured and enabled, recorded as the reference build for the rest of the sequence.
Wire one service to several processors
The flow owner creates a single enabled instance of the service at the controller level and points two or more processors at it through their service properties. Each processor is run against the same instance, and the team checks in the provenance and logs that the calls reach one shared client or pool rather than one per processor. Changes to the service configuration are then tested once and seen by all consumers. The step closes when the shared configuration is verified end to end on the development instance and the result is written into the change record for promotion.
Why the API and implementation NARs are split
Split the service API from its implementation so that processors depend only on the interface and never on the code that builds the connection pool, client or cache behind it. In NiFi, the API is a separate NAR that defines the ControllerService interface, and the implementation NAR contains the class that carries out the work. A processor that needs a shared database pool declares a dependency on the API NAR only. NiFi then supplies the concrete service at runtime through the service's own classloader, so the processor's bundle never has to ship or load the driver jars.
The packaging consequence matters most when several bundles use the same service. Each NAR gets its own classloader, and the implementation NAR's dependencies (drivers, HTTP clients, JSON libraries) stay inside that classloader. Processors that consume the service see only the API types, which avoids the version collisions that appear when two bundles bundle different copies of the same library. Before you release, confirm that every processor bundle lists the API NAR as its parent or dependency in the way the NiFi Developer's Guide describes, and that no processor imports a class from the implementation package directly. A quick check is to build a test flow where two processor bundles reference one service and see whether both start cleanly.
The Maven structure, parent NAR setup and classloader rules are covered in the sibling topic on packaging NiFi extensions as NARs, so this section does not repeat them. Read that topic before you choose a module layout, and treat the layout as a decision you record early, because moving a service into a different API NAR later changes the dependency graph of every processor that uses it.
What drives the cost and duration of a custom controller service
This table lists the factors that change how much work a custom controller service takes, with a way to keep each one down. Read it before writing a scope, because most of the effort comes from the number of external systems, the authentication modes and how many processors depend on the service. Reusing an existing service or narrowing the first release are the two largest levers.
| Driver | Why it changes effort | How to keep it down |
|---|---|---|
| Target systems and client libraries | Each system adds a client library, its own connection model and its own failure modes to test. | Start with one system per service and confirm whether an existing NiFi controller service already covers it. |
| Authentication modes | Each mode such as basic, token, mutual TLS or Kerberos adds configuration paths, credential handling and tests. | Support only the modes your environment uses today and confirm the rest as later scope. |
| Validation and error handling depth | Property validators, clear messages and handling of partial failures take more design work than a happy path. | Validate required properties and connectivity, and rely on documented error paths for the rest. |
| Thread safety and connection lifecycle | Shared clients must be safe under concurrent use, and connections must be opened, refreshed and closed on enable and disable. | Use the client library's own pooling and thread-safe objects where they exist instead of writing custom pools. |
| Number of processors sharing the service | A service used by many processors must behave predictably under load and across configuration changes, which raises testing and support effort. | Define a stable interface early so processors can share it without per-processor workarounds. |
| Test coverage with mocks | Mocked external systems, enable and disable cycles and failure cases each need test code that is maintained with the service. | Agree on a minimum test set, such as enable, disable, validation and one failure path, before the build starts. |
| Targeted NiFi version | Building for NiFi 2 instead of 1.x changes APIs, runtime requirements and packaging, and a service built for one line may need rework for the other. | Confirm the target version before design, and ask the NiFi version of your environment as part of scope. |
How validation and lifecycle code keeps shared services reliable
A controller service is only as reliable as its validation and lifecycle code, so treat those two areas as part of the design rather than as cleanup at the end. Validation runs when an operator configures the service, and NiFi uses the result to decide whether the service can be enabled at all. Put every check you can make from property values alone in the property descriptors and in the customValidate hook, so a bad connection URL, a missing credential or a conflicting pair of settings is reported in the UI before anyone enables the service. Failing fast at this point is far cheaper than discovering the mistake when processors start returning errors on live data. Keep the checks local and fast. Do not open a network connection during validation, because validation runs while the operator is editing and may run again on each change.
The @OnEnabled method should create the client, pool or cache and confirm it can be reached, and it should throw a clear exception when it cannot, so NiFi reports the failure instead of leaving a half-built service that processors call. The @OnDisabled and @OnShutdown methods must release every resource the service opened, including sockets, pooled connections, background threads and file handles, and they must tolerate a partially initialized state. When the external system drops, decide whether the service retries with a bounded backoff, surfaces the failure to the calling processor, or both. Document the choice in the property descriptions, because the behavior determines whether a flow queues data, routes it to failure or loses it. Confirm the exact annotation names and their ordering against the NiFi Developer's Guide for the NiFi version you target.
Verify these checks at two levels. Unit tests with the mock framework should cover invalid property combinations, the enable and disable sequence, and reconnect behavior against a stubbed external system. A staging cluster should then confirm what unit tests cannot: that the service enables on every node, that disabling it during active use leaves no open connections on the external system, and that a restart of the external dependency recovers without a NiFi restart. The nifi-mock testing topic covers the TestRunner harness, controller service registration and mocking in depth, so use that guide for the mechanics. Ask your team to confirm which failure modes are covered by automated tests and which depend on staging runs, and record that split in the release checklist.
How custom controller services connect to the rest of the platform
A controller service is shared infrastructure inside NiFi, so its effect reaches well beyond one processor. The connections below cover what consumes the service, how flows that depend on it are versioned, where credentials come from, how clustering treats it, and what changes at the NiFi 2 upgrade.
Processors that consume the service
Every processor that lists the controller service in a property inherits its lifecycle. Enabling, disabling and reconfiguring the service affects all of them at once, so a change to a shared connection pool can stop flows that look unrelated on the canvas. Before you change the API or the properties, list the consuming processors and their process groups. Confirm with your NiFi version how the UI reports dependent components when a service is disabled.
Flow registry and version control
When a process group that references a controller service is saved to a Flow Registry Client, the service definition and its property references travel with the flow version. NiFi 2 includes Flow Registry Clients for GitHub and GitLab, which store flows in a Git repository. Check that the service is defined in the same process group or an ancestor, and that a second environment has an equivalent service before you promote the flow.
Parameter contexts for credentials
Connection strings, API keys and passwords should reach a controller service through a parameter context rather than a literal property value. Mark the parameters as sensitive so the values are not shown in the UI after entry. Ask your team which parameter contexts hold production values, who can read them, and how they are populated in each environment, because the service only works where the parameter resolves.
Clustered nodes each run the service
In a NiFi cluster, each node enables its own instance of a controller service and opens its own connections to the external system. Pool sizes, connection limits and timeouts therefore multiply by the node count, which matters for databases and rate-limited APIs. Confirm the cluster coordination option you use, whether embedded or external ZooKeeper or Kubernetes, and test how a service behaves when a node restarts or rejoins.
Restricted components and access policies
A custom controller service that opens network connections, reads files or runs code may fall under NiFi's restricted component policies. Operators with limited permissions may be unable to create or enable it, and that can block a deployment that worked in development. Review the authorization policies for the component and the users who will configure it. Confirm the current restriction model in the security section of the administration guide for your release.
The NiFi 2 upgrade path
Controller services built for NiFi 1.x need review before a move to NiFi 2. The migration guidance requires upgrading to NiFi 1.27.0 before moving to 2.0.0, and it notes that the distributed map cache components were renamed. Inventory every custom service, check its dependencies against the newer runtime, and plan a test run of the flow. The 1.x line reaches end of support, so the upgrade is a planning item, not an option to defer indefinitely.
Questions teams ask before building a custom controller service
Does an existing NiFi controller service already cover what we need?
Usually check the bundled controller services and the processors that already use them before writing code. A controller service is worth building when several processors need the same connection pool, client, cache or credential handling and no bundled service or community extension fits the target system. Ask whether the gap is a missing protocol, a missing authentication method, or only a missing property on an existing service. Small gaps often belong in a processor or a configuration change, while a shared client that many flows must reuse is the case where a controller service earns its place. Confirm the exact NiFi version you run, because component names and behavior can differ between the 1.x and 2.x lines.
What drives the cost and effort of a custom controller service?
The main drivers are the complexity of the target system's client library, how many authentication and TLS modes must be supported, the number of properties and their validation rules, and how much of the lifecycle (enable, disable, reconnect, failure) must be handled correctly. Effort also grows with the number of processors that will share the service, because thread safety and resource release must hold under concurrent use. Testing against real or realistic copies of the target system adds effort, as does packaging the API and implementation as separate NARs. The cost components are internal engineering time, specialist time for the target system, test infrastructure, and documentation. Ask any partner for a quote that lists these items separately and states what is one-time and what must be maintained after each NiFi upgrade.
How long does a custom controller service usually take to build?
Duration depends on the target system, the number of supported features, and how much verification is required before production. A narrow service with one authentication method and a well-documented client library takes far less time than one that must handle failover, token refresh, several TLS configurations and sensitive properties. Dependencies include access to a test instance of the target system, a decision on which NiFi line the service must support, and sign-off on property names that flows will depend on. No duration is stated here because no researched figure applies. Ask for a timeline tied to a written scope with named milestones, and confirm which items depend on your team's availability.
What must our team provide before a controller service build starts?
The build goes faster when your team supplies the target system's API documentation, the client library version you have approved, and a non-production instance that behaves like production. Provide the authentication model in writing, including token lifetimes, certificate authorities and any secret store the service must read from. You also need a list of the processors that will use the service, the expected concurrency, and the properties flow designers should be able to set. Access to a test harness, a Git repository for the code, and a named reviewer on your side are typically required. Confirm these items in the statement of work so that missing inputs do not become scope changes.
Can our own engineers build a custom controller service?
Yes, if your team has Java experience, understands the NiFi extension model, and can operate a NiFi test environment. The work requires knowledge of the NiFi Developer's Guide patterns for the API and implementation split, lifecycle annotations, property validation and controller service sharing. Engineers also need enough familiarity with the target system to handle failures, retries and credential rotation correctly. Teams without that mix often find the risk in concurrency and resource cleanup, not in the initial code. A reasonable approach is to have your engineers build the first service with a partner review, or to hand over a fixed-scope build and keep ownership of the code afterward. Confirm who will maintain the NAR across NiFi upgrades.
How does NiFi 2 change the work on a custom controller service?
NiFi 2 changes the upgrade path more than the core design of a controller service. The NiFi migration guidance requires upgrading to NiFi 1.27.0 before moving to 2.0.0, and it changes the flow configuration file from flow.xml.gz to flow.json.gz. The variable registry expression language scope was removed and replaced by an ENVIRONMENT scope, and distributed map cache components were renamed, so any service that depends on those names must be reviewed. Plan to check the API and implementation NARs against the NiFi version you run, and test sharing across processors on that version. NiFi 1.x can no longer be maintained further because of its dependency stack, so new services should target the 2.x line. Confirm the specific NiFi version and extension API you need before writing code.
What should we check before shipping a controller service to production?
Check that the service validates its properties before enabling, releases connections when disabled, and recovers cleanly after the target system restarts. Confirm that sensitive properties are marked as sensitive, that secrets do not appear in logs or flow definitions, and that the service works when several processors use it at once. Test failure paths, including expired credentials and unreachable endpoints, and confirm the bulletins that operators will see are clear. Also verify that the NAR loads on a clean NiFi instance with the expected dependencies, and that the service behaves the same after a restart and a node join in a cluster. Record these checks as evidence for change management, since auditors reviewing NiFi change control often ask for them.
What the team should receive at handover
The handover should give your team everything needed to rebuild, test, configure and retire the service without asking the original developers. That means the source for both the API NAR and the implementation NAR as separate Maven modules, a build and test guide that starts from a clean checkout, and a configuration guide that lists every property of the controller service with its allowed values, default, whether it is sensitive, and which validators apply. Ask for the test results from the nifi-mock suite and any integration runs against a real backing system, with the exact NiFi version and Java runtime used to produce them. Confirm that the NAR coordinates match the NiFi version your environment runs, because the API and implementation NARs must be deployed together and reference the same API version.
The decision record is the document most often missing from a handover, and it matters for the next person who changes the service. It should explain why the connection pool, client or cache sits in a controller service instead of inside each processor, which processors are expected to share one instance, and what breaks if two flows configure separate instances against the same backend. Ask for the note on lifecycle decisions too: what the enable and disable methods release, how a failed validation is reported, and whether any state is held across processors. The runbook should cover enabling and disabling the service in a running cluster, the order to stop dependent processors, and the step-by-step rotation of credentials, including where secrets are read from and how a rotation is verified before old credentials are revoked.
After handover, your team owns the maintenance of the service, and the work recurs with each NiFi release. Plan for reviewing the controller service API on every NiFi minor upgrade, rerunning the test suite against the new version, and checking the deprecation notes in the NiFi migration guidance for any API or property changes. Assign an owner for the backing system's client library, since its security patches will not arrive through NiFi. Confirm with DeployOpen before the engagement starts what support follows the handover, and whether it is a fixed-scope fix or a retainer, since that is agreed in writing with the scope. Record the owner of each runbook step so that credential rotation and service changes remain possible when the original team is no longer involved.
How DeployOpen works on this
DeployOpen plans, builds, tests, documents and supports NiFi controller services inside your own infrastructure. Each engagement starts with a written scope that names the services, the processors that will share them, and the environments involved, so the work is agreed before the build begins.
How we scope
- Inventory every processor that needs the shared pool, client or cache before choosing a controller service.
- Agree in writing which API NAR, implementation NAR and property set are in and out of the engagement.
- Record the NiFi version, Java runtime and target clusters so the build matches the environment you run.
How we build
- Separate the controller service API from its implementation, then write lifecycle methods that open and close resources cleanly.
- Write validation rules that reject bad property combinations before a flow starts, with tests covering each rule.
- Test one service shared across several processors, including enable, disable and restart behavior on a cluster.
How we hand over
- Deliver source, build files and packaged NARs together with a runbook describing configuration and expected behavior.
- Document each property, its validation messages and the processors that depend on the service.
- Walk your platform and engineering teams through deployment, rollback and the upgrade steps for future NiFi releases.
How we support
- Ongoing support, upgrades and continuous testing for custom services are available as a retainer.
- Retesting after NiFi upgrades, including changes to controller service behavior between 1.x and 2.x, can be included in that retainer.
- Ask for the support boundary, response expectations and upgrade responsibilities in the written scope before signing.

