languagetool-org/languagetool · error · RuntimeException

Constructor of filter class '${className}' must have exactly

Error message

Constructor of filter class '${className}' must have exactly one constructor, but it has ${constructors.length}

What it means

RuleFilterCreator.getFilter() loads a RuleFilter class by name and instantiates it reflectively, caching per class. It requires the class to expose exactly one public constructor; if getConstructors() returns more or fewer than one, it throws RuntimeException naming the class and the number of constructors found.

Source

Thrown at languagetool-core/src/main/java/org/languagetool/rules/patterns/RuleFilterCreator.java:49

 */
public class RuleFilterCreator {
  private static final RuleFilterCreator INSTANCE = new RuleFilterCreator();

  private final Map<Class<?>, RuleFilter> myFilterCache = new ConcurrentHashMap<>();

  private RuleFilterCreator() {
  }

  /**
   * @param className fully qualified class Name of a class implementing {@link RuleFilter}
   */
  public RuleFilter getFilter(String className) {
    try {
      Class<?> aClass = JLanguageTool.getClassBroker().forName(className);
      return myFilterCache.computeIfAbsent(aClass, clazz -> {
        Constructor<?>[] constructors = clazz.getConstructors();
        if (constructors.length != 1) {
          throw new RuntimeException("Constructor of filter class '"
            + className + "' must have exactly one constructor, but it has " + constructors.length);
        }
        Constructor<?> constructor = constructors[0];
        try {
          if (constructor.getParameterTypes().length != 0) {
            throw new RuntimeException("Constructor of filter class '" + className + "' must not have arguments: " + constructor);
          }
          Object filter = constructor.newInstance();
          if (filter instanceof RuleFilter) {
            return (RuleFilter) filter;
          } else {
            throw new RuntimeException("Filter class '" + className + "' must implement interface " + RuleFilter.class.getSimpleName());
          }
        } catch (Exception e) {
          throw new RuntimeException("Could not create filter class using constructor " + constructor, e);
        }
      });
    } catch (ClassNotFoundException e) {

View on GitHub (pinned to 2e990059ce)

Solutions

  1. Keep exactly one public no-argument constructor in the filter class and remove or make non-public any other constructors
  2. Check the class named in the <filter class='...'> attribute — make sure it is your filter, not a wrapper with extra constructors
  3. If overloads are needed, keep one public no-arg constructor and delegate the overloads through private factory methods

Example fix

// before
public MyFilter() {}
public MyFilter(String config) { ... }
// after
public MyFilter() {}
private static MyFilter create(String config) { ... }
Defensive patterns

Strategy: validation

Validate before calling

Class<?> c = Class.forName(className);
int n = c.getConstructors().length;
if (n != 1) throw new IllegalStateException(className + " must have exactly one public constructor, found " + n);

Type guard

boolean isUsableFilterClass(Class<?> c) { return RuleFilter.class.isAssignableFrom(c) && c.getConstructors().length == 1 && c.getConstructors()[0].getParameterCount() == 0; }

Try / catch

try {
  RuleFilter f = new RuleFilterCreator().getFilter(className);
} catch (RuntimeException e) {
  if (e.getMessage().contains("must have exactly one constructor")) {
    throw new ConfigurationException("Bad filter class " + className + ": " + e.getMessage(), e);
  }
  throw e;
}

Prevention

When it happens

Trigger: Referencing (via <filter class='...'/>) a filter class that declares multiple public constructors, or zero public constructors (e.g. only package-private ones, or an implicit default blocked by a private/protected-only ctor).

Common situations: Adding a second convenience constructor to a filter class while an old no-arg public one still exists; making the constructor package-private during refactoring so getConstructors() returns 0; third-party filter classes with overloaded constructors.

Related errors


AI-assisted analysis of languagetool-org/languagetool@2e990059ce (2026-09-06). Data as JSON: /api/errors/b423eaae666639b8. Report an issue: GitHub.