quarkusio/quarkus · error · MessageBundleException
@Localized must be declared on an interface: {localized}
Error message
@Localized must be declared on an interface: {localized} What it means
@Localized may only be placed on interfaces; it marks a translated message bundle variant. The build-time processor throws when @Localized targets a class, enum, or record, because localization only makes sense as an interface extending a message bundle interface.
Source
Thrown at extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java:268
bundles.add(new MessageBundleBuildItem(name, bundleClass, localeToInterface,
localeToFiles, localeToMergeCandidates, defaultLocale));
} else {
throw new MessageBundleException("@MessageBundle must be declared on an interface: " + bundleClass);
}
}
}
// Detect interfaces annotated with @Localized that don't extend a message bundle interface
for (AnnotationInstance localizedAnnotation : index.getAnnotations(Names.LOCALIZED)) {
if (localizedAnnotation.target().kind() == Kind.CLASS) {
ClassInfo localized = localizedAnnotation.target().asClass();
if (Modifier.isInterface(localized.flags())) {
if (!localizedInterfaces.contains(localized.name())) {
throw new MessageBundleException(
"A localized message bundle interface must extend a message bundle interface: " + localized);
}
} else {
throw new MessageBundleException("@Localized must be declared on an interface: " + localized);
}
}
}
// Generate implementations
// name -> impl class
Map<String, ClassDesc> generatedImplementations = generateImplementations(bundles, generatedClasses, generatedResources,
generatedServiceProviders, messageTemplateMethods, index);
// Register synthetic beans
for (MessageBundleBuildItem bundle : bundles) {
ClassInfo bundleInterface = bundle.getDefaultBundleInterface();
beanRegistration.getContext().configure(bundleInterface.name())
.addType(bundle.getDefaultBundleInterface().name())
// The default message bundle - add both @Default and @Localized
.addQualifier(DotNames.DEFAULT).addQualifier().annotation(Names.LOCALIZED)
.addValue("value", getDefaultLocale(bundleInterface.declaredAnnotation(Names.BUNDLE), locales)).done()
.unremovable()View on GitHub (pinned to e1c734241f)
Solutions
- Move @Localized (and @Locale) onto an interface that extends the message bundle interface.
- Remove @Localized from the class; the localized variants are generated for you from bundle interfaces plus .properties files if you prefer file-based translation.
- Rebuild after moving the annotation.
Example fix
// before
@Localized
@Locale("de")
public class AppMessagesDe implements AppMessages {...}
// after
@Localized
@Locale("de")
public interface AppMessagesDe extends AppMessages {
@Override
String hello(String name);
} Defensive patterns
Strategy: validation
Validate before calling
Class<?> c = AppMessagesDe.class;
if (c.getAnnotation(Localized.class) != null && !c.isInterface()) {
throw new IllegalStateException("@Localized must be declared on an interface: " + c);
} Type guard
static boolean isLocalizedInterface(Class<?> c) {
return c.isInterface() && c.isAnnotationPresent(Localized.class);
} Prevention
- Only apply @Localized/@Locale to interfaces
- Use file-based (.properties) localization when interfaces are impractical
- Review annotations after any interface-to-class refactoring
When it happens
Trigger: Putting @Localized (with @Locale) on a concrete class or abstract class instead of an interface that extends a @MessageBundle interface.
Common situations: Mistakenly annotating the implementation class; IDE quick-fix applying annotations to the wrong type; refactorings that converted an interface to a class while keeping annotations.
Related errors
- A localized message bundle interface must extend a message b
- Default bundle method not found on %s: %s
- Message bundle name [%s] declared on %s must be a valid name
- Message bundle interface name conflict - [%s] is used for bo
- Locale of [%s] conflicts with the locale [%s] of the default
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/1d785dc6cd4c6d48.
Report an issue: GitHub.