FLX-1009¶
cannot find a matching constructor for com.example.RunningCount — the fields [countBySymbol] look like node-local state rather than references to other nodes
| Severity | ERROR |
| Category | GRAPH_MODEL |
| Element | NODE |
The rule¶
Every constructor-mapped field must be accepted by a constructor, or explicitly excluded from generated-source reproduction.
Why the compiler says this¶
By default Fluxtion constructor-maps every FINAL, non-transient instance field that is not @FluxtionIgnore, because generating a node's source means reproducing its state — and a final field can only be set by a constructor. @ConstructorArg on a field, or @AssignToField on a constructor parameter naming it, is an explicit escape hatch that constructor-maps that field regardless of finality. The listed values are renderable, but no constructor accepts them. For derived local state, reproducing the builder instance's current value is often not what the author intended; for configuration state, it may be essential. A non-final field that is not explicitly opted into constructor mapping can instead be wired through a setter.
How to fix it¶
For any listed field explicitly mapped by @ConstructorArg (import com.telamin.fluxtion.runtime.annotations.builder.ConstructorArg) or a parameter's @AssignToField (import com.telamin.fluxtion.runtime.annotations.builder.AssignToField), remove that opt-in before choosing the exclusion or setter route; explicit constructor mapping takes precedence. If this constructor-mapped value is derived local state — a map, a counter, a buffer the node builds for itself — mark it transient or @FluxtionIgnore (import com.telamin.fluxtion.runtime.annotations.builder.FluxtionIgnore) and Fluxtion will stop mapping it: [countBySymbol]. Excluding a field stops Fluxtion supplying its VALUE. A field that builds its own — an initializer, a counter, an empty map — is unaffected: its initializer still runs in the generated source. Only a value the builder put there before generation is lost. The compiler checked the remaining mapped fields against the available constructors, so this exclusion is a complete repair. If the state really should come from the graph, either add a constructor on RunningCount that accepts it, or make the field NON-FINAL, remove any @ConstructorArg/@AssignToField opt-in for it, and give it a JavaBean setter. Fluxtion setter-wires non-final fields that were not explicitly opted into constructor mapping.
Example¶
Both examples are compiled and put through a real Fluxtion.compile on every build, so the first is guaranteed to produce this diagnostic and the second to build cleanly. Note that the failing example is valid Java — javac is happy, and Fluxtion refuses it for its own reasons.
The code that causes it¶
package com.telamin.fluxtion.builder.diagnostic.examples;
import com.telamin.fluxtion.builder.generation.config.EventProcessorConfig;
import com.telamin.fluxtion.runtime.annotations.OnEventHandler;
import java.util.HashMap;
import java.util.Map;
/** FLX-1009 — a final field holding state the node built for itself. */
public class Flx1009Failing {
public static class RunningCount {
// FINAL and not excluded, so Fluxtion tries to reproduce its VALUE through a constructor —
// but this map is derived local state, not something the graph supplies.
private final Map<String, Integer> countBySymbol = new HashMap<>();
@OnEventHandler
public boolean onSymbol(String symbol) {
countBySymbol.merge(symbol, 1, Integer::sum);
return true;
}
}
public static void graph(EventProcessorConfig config) {
config.addNode(new RunningCount(), "runningCount");
}
}
The code that fixes it¶
package com.telamin.fluxtion.builder.diagnostic.examples;
import com.telamin.fluxtion.builder.generation.config.EventProcessorConfig;
import com.telamin.fluxtion.runtime.annotations.OnEventHandler;
import com.telamin.fluxtion.runtime.annotations.builder.FluxtionIgnore;
import java.util.HashMap;
import java.util.Map;
/** FLX-1009 fixed — the derived state is excluded from constructor mapping. */
public class Flx1009Fixed {
public static class RunningCount {
// Excluded, so Fluxtion stops trying to supply its value. The initializer still runs in the
// generated source, so the node starts with an empty map exactly as it does here.
// 'transient' has the same effect if you prefer no annotation.
@FluxtionIgnore
private final Map<String, Integer> countBySymbol = new HashMap<>();
@OnEventHandler
public boolean onSymbol(String symbol) {
countBySymbol.merge(symbol, 1, Integer::sum);
return true;
}
}
public static void graph(EventProcessorConfig config) {
config.addNode(new RunningCount(), "runningCount");
}
}
Which builds raise it¶
Source-generating (AOT) builds only. An interpreted or in-process build runs from the live node instances and never renders them as Java source, so it does not raise this. A graph developed interpreted can therefore meet this rejection for the first time at AOT build — that is by design, not a regression.
Reading it programmatically¶
This diagnostic is also written to the machine-readable report — the opt-in sidecar (-Dfluxtion.diagnostics.sidecar=true), or FluxtionDiagnostics.capture(...) for a caller that wants the objects:
{
"code": "FLX-1009",
"severity": "ERROR",
"category": "GRAPH_MODEL",
"element": { "kind": "NODE", … }
}
Select findings by severity, never by position: a failing build's report also contains any warnings it found, and diagnostics[0] is not the cause of the failure.
This page is generated from the compiler itself, so the wording above is exactly what a build emits. Do not edit it by hand — it is overwritten whenever the diagnostics are regenerated.