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
- Keep exactly one public no-argument constructor in the filter class and remove or make non-public any other constructors
- Check the class named in the <filter class='...'> attribute — make sure it is your filter, not a wrapper with extra constructors
- 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
- Give every RuleFilter exactly one public no-argument constructor
- Keep other constructors package-private or use static factories
- Add a unit test that loads all filter classes referenced by your rule XMLs
- Check <filter class='...'> names against the classpath package to avoid loading the wrong class
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
- Constructor of filter class '${className}' must not have arg
- Format error in file ${path}, line: ${line}
- No IssueType found for name '" + name + "'. Valid values: "
- The number of forms and postags has to be the same in disamb
- Set 'no', 'regexp' and 'postag_regexp' for filter PartialPos
AI-assisted analysis of languagetool-org/languagetool@2e990059ce (2026-09-06).
Data as JSON: /api/errors/b423eaae666639b8.
Report an issue: GitHub.