quarkusio/quarkus · error · IllegalStateException

Invalid tag: {{entry}} (expected key=value)

Error message

Invalid tag: {{entry}} (expected key=value)

What it means

VirtualThreadCollector parses JVM virtual-thread event tags from string entries and expects each entry in 'key=value' form. When a split on '=' does not yield exactly two parts, the collector cannot build a Micrometer Tag and throws IllegalStateException to fail fast rather than emit a malformed metric tag.

Source

Thrown at extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/virtualthreads/VirtualThreadCollector.java:83

     *
     * @param tags the tags.
     * @return the binder, {@code null} if the instantiation failed.
     */
    public MeterBinder instantiate(List<Tag> tags) {
        try {
            Class<?> clazz = Class.forName(VIRTUAL_THREAD_BINDER_CLASSNAME);
            return (MeterBinder) clazz.getDeclaredConstructor(Iterable.class).newInstance(tags);
        } catch (Exception e) {
            throw new IllegalStateException("Failed to instantiate " + VIRTUAL_THREAD_BINDER_CLASSNAME, e);
        }
    }

    private Tag createTagFromEntry(String entry) {
        String[] parts = entry.trim().split("=");
        if (parts.length == 2) {
            return Tag.of(parts[0], parts[1]);
        } else {
            throw new IllegalStateException("Invalid tag: " + entry + " (expected key=value)");
        }
    }

    public MeterBinder getBinder() {
        return binder;
    }

    public List<Tag> getTags() {
        return tags;
    }

    public void init(@Observes StartupEvent event) {
        if (enabled && binder != null) {
            binder.bindTo(registry);
        }
    }

    public void close(@Observes ShutdownEvent event) {

View on GitHub (pinned to e1c734241f)

Solutions

  1. Ensure each entry contains exactly one '=' separating a non-empty key and value
  2. Trim the entry before passing it (the method trims internally, but '=b' or 'a=' still fails)
  3. Filter out malformed entries before building tags, e.g. entries.stream().filter(e -> e.indexOf('=')>0)
  4. Log or skip unparseable entries instead of passing them to the collector

Example fix

// before
Tag tag = createTagFromEntry("threadName-12"); // throws
// after
String entry = "threadName-12";
if (entry.indexOf('=') > 0) {
    Tag tag = createTagFromEntry(entry);
} else {
    // skip or log
}
Defensive patterns

Strategy: validation

Validate before calling

boolean isValidTagEntry(String entry) {
    String t = entry == null ? "" : entry.trim();
    int i = t.indexOf('=');
    return i > 0 && i < t.length() - 1 && t.indexOf('=', i + 1) == -1;
}

Type guard

boolean isWellFormedKeyValue(String s) {
    return s != null && s.trim().split("=").length == 2 && s.contains("=");
}

Try / catch

try {
    Tag tag = createTagFromEntry(entry);
} catch (IllegalStateException e) {
    log.warnf("Skipping malformed tag entry: %s", entry);
}

Prevention

When it happens

Trigger: Calling createTagFromEntry (directly or via the collector's binder) with an entry string that has no '=', more than one '=', or an empty value such as 'keyOnly', 'a=b=c', or '=value'.

Common situations: Feeding custom or whitespace-padded event labels into the virtual-thread metrics collector; passing entries copied from a different format (e.g. JSON 'key: value'); manual tag construction in tests.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/95c6320530a96910. Report an issue: GitHub.