Data hub instead of a hatch: Pulsar Functions in the data stream

Data hub instead of a hatch: Pulsar Functions in the data stream
By Matthias Petermann / on 19.12.2025

Introduction: From the message bus to the data hub

Event streaming platforms are still considered necessary but largely passive infrastructure in many architectures. They transport messages from A to B, decouple systems temporally and, ideally, ensure reliability and scalability. However, this is often where their role ends.

Apache Pulsar allows – under clear technical and organizational assumptions – a different approach. The platform can not only transport messages, but also become a central data hub, where data access, simple decision logic and governance coincide.

The idea behind it is pragmatic: If data flows through a central system anyway, certain rules, selections and simple transformations can take place there - provided they affect the data flow (and do not represent core technical logic), and provided ownership, operation and boundaries are clearly defined.

This article shows how Pulsar Functions implement this approach, under what conditions it proves itself – and where conscious limits must be drawn.

Apache Pulsar: Architecture and basic concepts

In order to be able to classify Pulsar Functions in a meaningful way, it is worth taking a quick look at the central terms.

Brokers are the active nodes of the system. They receive messages, manage topics and subscriptions and – optionally – execute Pulsar functions. Importantly, brokers are deliberately designed to be low-state and can be scaled horizontally. Persistent state lies outside the broker (e.g. in BookKeeper).

Tenants form the highest organizational and security boundary. They clearly separate clients, organizational units or projects from each other.

Namespaces further structure tenants. They bundle topics, subscriptions, retention rules, quotas and ACLs - and are therefore a clearly defined data space. In practice, the namespace is often the central unit for organizing deployments, limits and responsibilities for functions.

Topics are persistent event streams. Messages are not simply passed on, but are stored permanently and made available for consumption in a time-decoupled manner.

Subscriptions define how topics are read. They are not a detail, but a core governance tool for access, semantics, repeatability and load balancing.

ℹ️ Infrastructure, not application
These concepts are part of the infrastructure. They describe the controlled data space in which applications are allowed to move - regardless of their implementation.

Pulsar Functions: Compute in the data stream – with clear boundaries

Pulsar Functions expands this model to include targeted compute. They allow messages to be sent directly in the data stream:

  • filter
  • to forward (routing)
  • to transform
  • to be technically enriched

This is not about moving any technical logic into the broker. Pulsar functions address exactly the logic that is directly connected to the data flow - i.e. where decisions at the data edge would otherwise be distributed and difficult to control.

💡 Central feature
Pulsar Functions operate within the Pulsar ecosystem and are not a separate service that exists “alongside”. This moves data flow logic into an observable, measurable and controllable context.
⚠️ What doesn't belong in Functions
Complex technical logic, long or blocking I/O (e.g. synchronous API calls per message), extensive state, compute-intensive jobs or “workflows” generally do not belong in Pulsar Functions. If a function becomes a mini-microservice, it is too big.

Operationally: A function is code in the data path. It can improve the flow of data – or, if discipline is poor, destabilize it. This is not an argument against functions, but an argument for clear rules, ownership and guardrails.

Case study: Digital products in the ERP data stream

Let’s look at a typical scenario from practice.

A proprietary ERP system mediates all business transactions via a message bus. In order to execute an order, it expects a message with a shopping cart from the shop system. Thousands of such orders are received every second. There is also a delivery system for digital products such as eBooks or MP3 downloads. This system is only interested in orders that contain at least one digital product, in order to then provide the customer with a download link.

On a “dumb” message bus, the delivery system would have to consume all orders, parse each message and decide for itself whether it is relevant.

This typically leads to:

  • unnecessary network traffic (all data everywhere)
  • wasted computing power at the edge (every consumer makes the same selection)
  • proprietary formats that migrate deep into consuming systems (coupling)
  • Debugging and governance problems (“Who filters where – and why?”)

Adapting the ERP system is - which is not uncommon - often expensive, time-consuming or simply not possible.

The Pulsar solution: selection in flight – under governance

Apache Pulsar allows this logic to be moved to where it has the most impact: into the data stream itself – as long as it stays close to the data flow and is operated cleanly.

A Pulsar Function analyzes incoming orders, recognizes digital products and forwards only relevant messages to a dedicated topic. Optionally, a transformation into a neutral, manufacturer-independent specialist format can already take place at this point.

⚠️ The usual objection
“Then there is additional logic that needs to be maintained and owned.”

That is correct. The alternative, however, is not “no logic”, but rather a (usually less controllable) distribution:

  • Selection in each consumer (diffuse, difficult to audit)
  • additional bridge services (more infrastructure, own lifecycle)
  • or pressure on the ERP manufacturer (often unrealistic)
ℹ️ Ownership: Who owns the function?
A stream function should organizationally belong to the platform / integration (e.g. Data Platform Team), not for individual specialist applications. This makes it clear who reviews changes, deploys them and reacts in the event of an incident.

Alternatives in comparison – fairly ranked

approach Advantages Disadvantages Systemic effects
🔴 Proprietary format up to the consumer No additional stream logic, simple producers Each consumer must read and parse all messages, high coupling High network traffic, CPU load in all consumers, scales poorly with number of subscriptions
🔴 Additional Bridge Services Central selection possible, professional consumers relieved Additional services, own operations and lifecycle Double data movement, additional latency, new failure domain
🔴 Customization by ERP manufacturer Selection at origin, clean API Often unrealistic, long lead times, external dependency High project costs and time-to-market risk
🟢 Pulsar Function Selection before fan-out, low latency, auditable Code in data path, disciplined debugging required Unique selection in the stream, minimal network and consumer load with clear guardrails

Function Development

The actual function is deliberately implemented in Java: It is established in many companies and fits well into existing build and operational processes.

The model is more important than the language: A Pulsar Function is a small, clearly defined unit that makes exactly one decision in the data stream. It implements the Function<I, O> interface and has no infrastructure of its own, no server of its own and no “service lifecycle” of its own.

The following function only checks incoming orders to see whether they contain digital products. The example is deliberately kept simple to focus on the principle - not on parsing or schema details.

package net.d2ux.stream.order.filter;

import org.apache.pulsar.functions.api.Context;
import org.apache.pulsar.functions.api.Function;

public final class DigitalOrderFilter implements Function<String, String> {

    private static final String KEYWORD = "digital";

    @Override
    public String process(String input, Context context) {

        if (input == null || input.isBlank()) {
            context.getLogger().debug("Skipping empty message");
            return null;
        }

        // echte Systeme nutzen Schema (JSON/Avro) + robuste Validierung.
        if (input.toLowerCase().contains(KEYWORD)) {
            context.getLogger().info("Digital order detected");
            return input;
        }

        return null;
    }
}
ℹ️ Return null means: filter
In Pulsar Functions, return null means that the message will be discarded.
⚠️ Production note
In practice, this typically includes at least: Schema (e.g. JSON/Avro), keys, validation, DLQ/dead letter strategy and tests. The example deliberately only shows the decision model.

Build and deploy the function

The function is built using Maven. A complete project therefore includes a pom.xml, a clean package structure and (ideally) tests and CI definitions.

For the example, a simple build step that creates an executable JAR is sufficient:

mvn clean install

The result is a single JAR. Deployment takes place via the Pulsar Shell:

$ pulsar-shell
default(localhost)> admin functions create \
  --tenant public \
  --namespace default \
  --name order.filter.digital \
  --jar /abs/path/filter-order-digital-1.0.0.jar \
  --classname net.d2ux.stream.order.filter.DigitalOrderFilter \
  --inputs persistent://public/default/orders.raw \
  --output persistent://public/default/orders.filtered.digital
ℹ️ Governance by design
The function is subject to the same organizational boundaries as topics and subscriptions: Tenant/namespace, ACLs, quotas, observability and (depending on the setup) deployment policies.

Governance specifically: Guardrails instead of uncontrolled growth

Compute in stream only becomes an advantage if it remains controlled. Typical guardrails that have proven themselves in practice:

  • Deployment rights: Only defined roles/teams are allowed to create/change functions.
  • Topic whitelist: Functions are only allowed to access shared topics (input/output).
  • Naming and versioning rules: Clearly identify what is “prod” and what is “experiment”.
  • Change Management: Review, CI, rollback plan.
  • Limits: Quotas and resource limit per namespace to protect the broker.

This is the point at which “governance” becomes concrete: not as a buzzword, but as a set of rules that make operations possible.

Tests in the data stream with the Pulsar CLI

The best way to test streaming systems is where they work: in the ongoing data flow. Instead of complex test harnesses, a deliberately simple approach that still tests realistic behavior is often sufficient.

The Pulsar CLI allows you to specifically start producers and consumers and thus observe the behavior of the function in interaction with brokers, topics and subscriptions.

In the first step, a consumer is started on the target topic to which the function forwards relevant messages:

$ export PULSAR_URL=pulsar://127.0.0.1:6650
$ pulsar-cli consumer -t 'persistent://public/default/orders.filtered.digital' -s debug

Producer on the source topic:

$ echo "Bestellung analog"  | pulsar-cli producer -t 'persistent://public/default/orders.raw'
$ echo "Bestellung digital" | pulsar-cli producer -t 'persistent://public/default/orders.raw'

Expected behavior: Only the digital order appears in the target topic.

ℹ️ Why this is a real test
This test not only validates code, but the complete data flow including brokers, topics/subs and the associated policies.

Monitoring and transparency

Pulsar exports comprehensive metrics for Functions. Latencies, throughput and errors are visible - not a black box.

Important: Debugging in stream is different than debugging in services. You need clear metrics, clean logs and defined SLOs. Then it becomes manageable.

Metric Meaning
pulsar_function_received_total Received messages
pulsar_function_processed_successfully_total Successful processing
pulsar_function_user_exceptions_total Error in function code
pulsar_function_process_latency_ms Processing latency

Conclusion

Pulsar Functions are not a replacement for microservices or any serverless feature. They are a precise tool for making clearly defined decisions in the data stream.

Those who respect their boundaries, clarify ownership and take governance seriously, can place compute where data flows anyway – with high transparency, auditable rules and often significantly less wastage than with edge selection in every consumer.

Apache Pulsar shows: A message bus can be more than transport – a manageable data hub, if you operate it as an infrastructure product (and not as a random script collection point).

🚀 Complete sample project

The complete Maven project including all examples shown here is available at:

https://forge.ext.d2ux.net/OpenLab/pulsar-stream-functions


Image credit: The Pulsar logo is a registered trademark of the Apache Software Foundation. Use in editorial reporting in accordance with official guidelines.