apache/seatunnel · error · IllegalArgumentException

MQTT QoS must be 0 (at-most-once) or 1 (at-least-once), got:

Error message

MQTT QoS must be 0 (at-most-once) or 1 (at-least-once), got: 

What it means

The MQTT sink writer validates the configured qos option in its constructor and rejects values other than 0 or 1 with an IllegalArgumentException. The sink deliberately forbids QoS 2 (exactly-once) and negative values because its publish/ack handling only supports at-most-once and at-least-once delivery.

Source

Thrown at seatunnel-connectors-v2/connector-mqtt/src/main/java/org/apache/seatunnel/connectors/seatunnel/mqtt/sink/MqttSinkWriter.java:69

public class MqttSinkWriter implements SinkWriter<SeaTunnelRow, Void, Void>, MqttCallback {

    private static final String CLIENT_ID_PREFIX = "seatunnel_mqtt_sink_task_";
    private static final long RETRY_BACKOFF_MS = 200L;

    private final String topic;
    private final int qos;
    private final int retryTimeoutMs;
    private final int batchSize;
    private final SerializationSchema serializationSchema;
    private final List<MqttMessage> messageBuffer;
    private MqttClient mqttClient;

    public MqttSinkWriter(
            SinkWriter.Context context, SeaTunnelRowType rowType, ReadonlyConfig pluginConfig) {
        this.topic = pluginConfig.get(MqttSinkOptions.TOPIC);
        this.qos = pluginConfig.get(MqttSinkOptions.QOS);
        if (this.qos < 0 || this.qos > 1) {
            throw new IllegalArgumentException(
                    "MQTT QoS must be 0 (at-most-once) or 1 (at-least-once), got: " + this.qos);
        }
        this.retryTimeoutMs = pluginConfig.get(MqttSinkOptions.RETRY_TIMEOUT);
        this.batchSize = pluginConfig.get(MqttSinkOptions.BATCH_SIZE);
        if (this.batchSize < 1) {
            throw new IllegalArgumentException("batch_size must be >= 1, got: " + this.batchSize);
        }
        this.messageBuffer = new ArrayList<>(this.batchSize);
        this.serializationSchema = createSerializationSchema(rowType, pluginConfig);

        // Each subtask appends its index and a random UUID to guarantee a globally unique client
        // ID,
        // preventing mutual disconnections and connection hijacking when running parallel jobs.
        String clientId =
                CLIENT_ID_PREFIX
                        + context.getIndexOfSubtask()
                        + "-"
                        + java.util.UUID.randomUUID().toString();

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Set qos = 0 for at-most-once delivery.
  2. Set qos = 1 for at-least-once delivery (recommended for sinks).
  3. Remove the qos option to use the default instead of an unsupported value.
  4. If exactly-once is required, MQTT QoS 2 is not supported by this sink — deduplicate downstream.

Example fix

// before
MqttSink {
  qos = 2
}
// after
MqttSink {
  qos = 1
}
Defensive patterns

Strategy: validation

Validate before calling

int qos = config.getInt("qos", 1);
if (qos < 0 || qos > 1) {
    throw new IllegalArgumentException("qos must be 0 or 1, got: " + qos);
}

Try / catch

try {
    new MqttSinkWriter(context, rowType, pluginConfig);
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("MQTT QoS must be")) {
        // fall back to qos = 1 or surface config error to user
    }
    throw e;
}

Prevention

When it happens

Trigger: Setting `qos = 2`, `qos = -1`, or any value outside [0,1] in the MQTT sink configuration; MqttSinkWriter constructor reads MqttSinkOptions.QOS and fails validation.

Common situations: Users copying QoS 2 settings from other MQTT tools; assuming the sink supports exactly-once; typos producing out-of-range numbers.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/4c284b4f043b45fd. Report an issue: GitHub.