nathanmarz/storm · error · RuntimeException

Trident only supports components that emit a single stream

Error message

Trident only supports components that emit a single stream

What it means

Trident requires every component (spout or bolt) it wraps to declare exactly one output stream. getSingleOutputStreamFields calls declareOutputFields and inspects the declaration map; if the component declares zero or multiple streams, Trident cannot build its topology graph, so it throws this RuntimeException from TridentUtils.getSingleOutputStreamFields (TridentUtils.java:71).

Solutions

  1. Refactor the component to declare exactly one output stream (use a single declare() call).
  2. Split the multi-stream component into separate single-stream components and wire them in Trident separately.
  3. If you need Storm's multi-stream semantics, use a plain Storm topology instead of Trident.
  4. If the declaration map is empty, add a declareOutputFields implementation that declares one stream.

Example fix

// before
public void declareOutputFields(OutputFieldsDeclarer declarer) {
    declarer.declareStream("a", new Fields("x"));
    declarer.declareStream("b", new Fields("y"));
}
// after
public void declareOutputFields(OutputFieldsDeclarer declarer) {
    declarer.declare(new Fields("x", "y"));
}
Defensive patterns

Strategy: validation

Validate before calling

OutputFieldsGetter getter = new OutputFieldsGetter();
component.declareOutputFields(getter);
if (getter.getFieldsDeclaration().size() != 1) {
    throw new IllegalArgumentException("Component must declare exactly one stream for Trident");
}

Type guard

boolean isTridentCompatible(IComponent c) {
    OutputFieldsGetter g = new OutputFieldsGetter();
    c.declareOutputFields(g);
    return g.getFieldsDeclaration().size() == 1;
}

Prevention

When it happens

Trigger: Calling Trident topology-building APIs (e.g. newStream(...).each/parallelismHint, TridentTopology operations) with a spout or bolt whose declareOutputFields registers more than one stream (e.g. two OutputFieldsDeclarer.declareStream calls, or a default declaration plus a named one).

Common situations: Reusing a plain Storm bolt that emits multiple named streams inside a Trident topology; copy-pasting a component that mixes declare() and declareStream(); building Trident topologies from legacy multi-stream spouts.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of nathanmarz/storm@cdb116e942 (2026-09-12). Data as JSON: /api/errors/e2d6c2738830b397. Report an issue: GitHub.

Appendix: source

Thrown at storm-core/src/jvm/storm/trident/util/TridentUtils.java:71

    }
    
    public static Fields fieldsSubtract(Fields all, Fields minus) {
        Set<String> removeSet = new HashSet<String>(minus.toList());
        List<String> toKeep = new ArrayList<String>();
        for(String s: all.toList()) {
            if(!removeSet.contains(s)) {
                toKeep.add(s);
            }
        }
        return new Fields(toKeep);
    }
    
    public static Fields getSingleOutputStreamFields(IComponent component) {
        OutputFieldsGetter getter = new OutputFieldsGetter();
        component.declareOutputFields(getter);
        Map<String, StreamInfo> declaration = getter.getFieldsDeclaration();
        if(declaration.size()!=1) {
            throw new RuntimeException("Trident only supports components that emit a single stream");
        }
        StreamInfo si = declaration.values().iterator().next();
        if(si.is_direct()) {
            throw new RuntimeException("Trident does not support direct streams");
        }
        return new Fields(si.get_output_fields());        
    }
    
    /**
     * Assumes edge contains an index
     */
    public static <T> List<T> getParents(DirectedGraph g, T n) {
        List<IndexedEdge> incoming = new ArrayList(g.incomingEdgesOf(n));
        Collections.sort(incoming);
        List<T> ret = new ArrayList();
        for(IndexedEdge e: incoming) {
            ret.add((T)e.source);
        }        

View on GitHub (pinned to cdb116e942)