Java Omnibus
OOAD & Architecture / Architectural Styles / Pipes and Filters Architecture
Pipes and Filters Pipelines Java Processing Flow
Architectural Styles

Pipes and Filters is most useful when the work itself is naturally a pipeline of transformations.

Not every system is best understood as controllers, services, and repositories. Some problems are processing pipelines: validate, enrich, transform, score, route, and emit. Pipes and Filters is a better mental model for those cases.

Why This Topic Matters

Why it matters in real Java systems

Batch imports, fraud screening, pricing pipelines, event enrichment, and document processing often work better as composable stages than as one large service with many conditional branches.

Core Explanation

The main architectural idea

Pipes and Filters treats the system as a chain of processing stages. Each filter performs one focused transformation, validation, or enrichment. The pipe carries the result to the next stage. This often leads to better composability and clearer operational reasoning for streaming or staged processing problems.

The style is less suitable when the primary difficulty is rich business modeling around stable aggregates. It works best when the dominant structure is flow and transformation.

Concept Breakdown

Key ideas to keep in mind

Concept

Best fit

Sequential processing pipelines, ingest flows, message enrichment, and staged transformations.

Concept

Strength

Each step can stay small, testable, and replaceable.

Concept

Risk

Once the pipeline needs too much shared mutable context, the style starts to fray.

Diagram in words

Diagram in words

Picture the flow: an order import enters validation, then customer enrichment, then pricing checks, then fraud scoring, then shipment planning, then persistence. Each stage receives a record, changes or annotates it, and passes it forward.
Java Example

Java example in the Order Management domain

Java example: order import processing pipeline
package org.javaomnibus.ecommerce.pipeline;

public interface Filter<T> {
    T apply(T input);
}

public final class ValidateOrderFilter implements Filter<OrderImportRecord> {
    @Override
    public OrderImportRecord apply(OrderImportRecord input) {
        if (input.lines().isEmpty()) {
            throw new IllegalArgumentException("Order must contain at least one line");
        }
        return input;
    }
}

public final class FraudScoreFilter implements Filter<OrderImportRecord> {
    @Override
    public OrderImportRecord apply(OrderImportRecord input) {
        return input.withFraudScore(42);
    }
}
Code Walkthrough

Step by step

  1. Each filter owns one stage in the processing flow.
  2. The pipeline becomes easy to reason about because each step is narrow.
  3. Stages can be added, removed, or reordered with care.
  4. This is especially powerful when the work is genuinely transformational.
Real-World Usage

Where this helps in practice

  • ETL and data-processing pipelines
  • Validation and enrichment flows
  • Security, observability, or request-processing chains
Trade-Offs

Trade-offs and when not to overuse it

  • Pipelines are elegant when the work is naturally staged, but awkward when the domain requires rich object collaboration and branching state.
  • Shared mutable context can make filters harder to reason about than they first appear.
Common Mistakes

Frequent mistakes to watch for

  • Forcing pipelines onto object-heavy domain logic that is not really sequential transformation
  • Letting filters depend too heavily on hidden side effects
  • Ignoring ordering and idempotency concerns between stages
  • Using a pipeline where an application service or state machine would be clearer
Related Pages

Related concepts


What To Read Next