JetBrains/intellij-community · error · ContractInheritedException

changeSignature.contract.converter.inherited.annotation

changeSignature.contract.converter.inherited.annotation

Error message

Annotation is inherited from base method

What it means

Thrown by ContractConverter.convertContract when the @Contract annotation found for a method is declared on a base method, not on the method being refactored (annotation.getOwner() != method.getModifierList() throws ContractInheritedException). Change Signature cannot rewrite a contract that is physically declared elsewhere in the hierarchy, since updating it would affect all overrides.

Source

Thrown at java/java-impl/src/com/intellij/refactoring/changeSignature/ContractConverter.java:39

import java.util.List;

public final class ContractConverter {
  private ContractConverter() {}

  public static @Nullable PsiAnnotation convertContract(@NotNull PsiMethod method, @NotNull JavaChangeInfo info) throws ContractConversionException {
    return convertContract(method, info.getOldParameterNames(), info.getNewParameters());
  }

  public static @Nullable PsiAnnotation convertContract(@NotNull PsiMethod method,
                                                        String @NotNull [] oldParameterNames,
                                                        JavaParameterInfo @NotNull [] newParameters) throws ContractConversionException {
    PsiAnnotation annotation = JavaMethodContractUtil.findContractAnnotation(method);
    if (annotation == null || AnnotationUtil.isInferredAnnotation(annotation)) return null;
    if (AnnotationUtil.isExternalAnnotation(annotation)) {
      throw new ContractConversionException(JavaRefactoringBundle.message("changeSignature.contract.converter.external.annotations"));
    }
    if (annotation.getOwner() != method.getModifierList()) {
      throw new ContractInheritedException();
    }
    if (annotation.findDeclaredAttributeValue(MutationSignature.ATTR_MUTATES) != null) {
      throw new ContractConversionException(JavaRefactoringBundle.message("changeSignature.contract.converter.mutation.contract"));
    }
    String text = AnnotationUtil.getStringAttributeValue(annotation, null);
    List<StandardMethodContract> contracts = Collections.emptyList();
    if (text != null) {
      try {
        contracts = StandardMethodContract.parseContract(text);
      }
      catch (StandardMethodContract.ParseException exception) {
        String definitionError = JavaRefactoringBundle.message("changeSignature.contract.converter.definition.error",
                                                               exception.getMessage());
        throw new ContractConversionException(definitionError);
      }
    }
    int[] newToOldIndex = StreamEx.of(newParameters).mapToInt(ParameterInfo::getOldIndex).toArray();
    int[] oldToNewIndex = reverseIndex(oldParameterNames.length, newToOldIndex);

View on GitHub (pinned to be881553f2)

Solutions

  1. Run Change Signature on the base method that actually declares the @Contract, so the annotation is updated together with the hierarchy.
  2. Remove the inherited contract dependency: declare (or delete) the contract on the base method explicitly before refactoring.
  3. Manually update the @Contract on the base method after the refactoring if the IDE refuses to convert it.

Example fix

// before
interface Base { @Contract("null -> false") boolean check(String s); }
class Impl implements Base { public boolean check(String s){...} } // refactor Impl -> throws
// after: refactor Base instead
interface Base { @Contract("null -> false") boolean check(@Nullable String s); }
Defensive patterns

Strategy: type-guard

Validate before calling

PsiAnnotation a = JavaMethodContractUtil.findContractAnnotation(method);
if (a != null && a.getOwner() != method.getModifierList()) {
  // contract is declared on a base method; refactor the base method instead
}

Type guard

static boolean ownsItsContract(@NotNull PsiMethod m) {
  PsiAnnotation a = JavaMethodContractUtil.findContractAnnotation(m);
  return a == null || a.getOwner() == m.getModifierList();
}

Try / catch

try { ContractConverter.convertContract(method, info); } catch (ContractInheritedException e) { /* locate declaring base method via findContractAnnotation owner and refactor that */ }

Prevention

When it happens

Trigger: Running Change Signature on an overriding method that inherits @Contract from a superclass or interface method (JavaMethodContractUtil.findContractAnnotation walks up the hierarchy); the contract needs conversion because parameters moved/changed.

Common situations: Interface methods annotated with @Contract and implementations being refactored; library base classes annotated upstream; changing a subclass method signature while the parent's contract still references old parameter positions.

Related errors


AI-assisted analysis of JetBrains/intellij-community@be881553f2 (2026-08-14). Data as JSON: /api/errors/8445d4d05f1724d0. Report an issue: GitHub.