quarkusio/quarkus · error · RuntimeException

has to be an interface

Error message

 has to be an interface

What it means

REST Data Panache resources in Quarkus must be Java interfaces; the processor scans for annotated candidates and validates each. A concrete class annotated as a REST Data Panache resource cannot be used to generate data-backed endpoints, so validateResource throws.

Source

Thrown at extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/main/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/HibernateReactivePanacheRestProcessor.java:131

            String idType = generics.get(2).name().toString();

            List<ClassInfo> listenersForEntityType = getListenersByEntityType(index, resourceMethodListeners, entityType);
            DataAccessImplementor dataAccessImplementor = new RepositoryDataAccessImplementor(repositoryClassName);
            String resourceClass = resourceImplementor.implement(
                    classOutput, dataAccessImplementor, resourceInterface, entityType, listenersForEntityType);
            // Make sure that repository bean is not removed and will be injected to the generated resource
            unremovableBeansProducer.produce(new UnremovableBeanBuildItem(
                    new UnremovableBeanBuildItem.BeanClassNameExclusion(repositoryClassName)));

            restDataResourceProducer.produce(new RestDataResourceBuildItem(
                    new ResourceMetadata(resourceClass, resourceInterface, entityType, idType,
                            getEntityFields(index, entityType))));
        }
    }

    private void validateResource(IndexView index, ClassInfo classInfo) {
        if (!Modifier.isInterface(classInfo.flags())) {
            throw new RuntimeException(classInfo.name() + " has to be an interface");
        }

        if (classInfo.interfaceNames().size() > 1) {
            throw new RuntimeException(classInfo.name() + " should only extend REST Data Panache interface");
        }

        if (!index.getKnownDirectImplementors(classInfo.name()).isEmpty()) {
            throw new RuntimeException(classInfo.name() + " should not be extended or implemented");
        }
    }

    private List<Type> getGenericTypes(ClassInfo classInfo) {
        return classInfo.interfaceTypes()
                .stream()
                .findFirst()
                .orElseThrow(() -> new RuntimeException(classInfo.toString() + " does not have generic types"))
                .asParameterizedType()
                .arguments();

View on GitHub (pinned to e1c734241f)

Solutions

  1. Convert the annotated type into an interface extending the appropriate REST Data Panache interface
  2. Remove the annotation from the class and implement endpoints manually
  3. Move the annotation to a new interface that targets the entity or repository

Example fix

// before
@PanacheEntityResource
public class CustomerResource {}
// after
@PanacheEntityResource
public interface CustomerResource extends PanacheMongoResource<Customer> {}
Defensive patterns

Strategy: validation

Validate before calling

if (!CustomerResource.class.isInterface()) throw new IllegalStateException("REST Data Panache resource must be an interface");

Prevention

When it happens

Trigger: A class (not an interface) carries a REST Data Panache resource annotation, found via findEntityResources or findRepositoryResources during augmentation.

Common situations: Applying the resource annotation to a class instead of an interface; Kotlin 'class' vs 'interface' mistake; misunderstanding that REST Data Panache only generates implementations of interfaces.

Related errors


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