JetBrains/intellij-community · error · ContractConversionException

changeSignature.contract.converter.external.annotations

changeSignature.contract.converter.external.annotations

Error message

Automatic update of external annotation is not supported

What it means

Thrown by ContractConverter.convertContract during Change Signature refactoring when a method's @org.jetbrains.annotations.Contract annotation is an external annotation (stored in an external annotations XML file rather than in the source). The IDE cannot automatically update external annotations, so the refactoring fails with ContractConversionException instead of silently leaving a stale contract.

Source

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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
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);
      }

View on GitHub (pinned to be881553f2)

Solutions

  1. Move the @Contract annotation into the source file (remove it from external annotations XML, add it above the method) and rerun Change Signature.
  2. Manually update the contract clause in the external annotations XML to match the new parameter order, then skip automatic conversion.
  3. Perform the signature change without contract conversion and fix the contract yourself afterwards.
  4. Exclude the annotated method from refactoring or revert parameter reordering that affects contract clauses.

Example fix

// before: external annotations XML
<method ...><annotation name='org.jetbrains.annotations.Contract'><val name='value' val='null -> false'/></annotation></method>
// after: inline in source
import org.jetbrains.annotations.Contract;
@Contract("null -> false")
public boolean check(@Nullable String s) { ... }
Defensive patterns

Strategy: try-catch

Validate before calling

PsiAnnotation ann = JavaMethodContractUtil.findContractAnnotation(method);
if (ann != null && AnnotationUtil.isExternalAnnotation(ann)) {
  // update external annotations XML manually or inline the annotation first
}

Type guard

static boolean isInlineContract(@NotNull PsiMethod m) {
  PsiAnnotation a = JavaMethodContractUtil.findContractAnnotation(m);
  return a == null || (!AnnotationUtil.isExternalAnnotation(a) && !AnnotationUtil.isInferredAnnotation(a) && a.getOwner() == m.getModifierList());
}

Try / catch

try { ContractConverter.convertContract(method, info); } catch (ContractConversionException e) { /* show message; for external annotations update the XML by hand */ }

Prevention

When it happens

Trigger: Running Change Signature on a method whose @Contract lives in external annotations (annotations/.../methods.xml next to a library, or an 'external annotations' directory configured in the module) while the signature change would require updating the contract.

Common situations: Projects that attach external annotations to third-party or read-only sources; libraries annotated via JetBrains external annotation sets; code where methods are annotated through an annotations repository rather than inline.

Related errors


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