diff --git a/java/java-impl/resources/META-INF/JavaPlugin.xml b/java/java-impl/resources/META-INF/JavaPlugin.xml
index 383d8dd82193..d3cb2ff0c4f8 100644
--- a/java/java-impl/resources/META-INF/JavaPlugin.xml
+++ b/java/java-impl/resources/META-INF/JavaPlugin.xml
@@ -1641,6 +1641,7 @@
+
diff --git a/java/java-impl/resources/messages/RefactorJBundle.properties b/java/java-impl/resources/messages/RefactorJBundle.properties
index c698ab501730..adc64a1e9db0 100644
--- a/java/java-impl/resources/messages/RefactorJBundle.properties
+++ b/java/java-impl/resources/messages/RefactorJBundle.properties
@@ -41,3 +41,6 @@ referenced.element.out.of.project={0} is out of project
unable.to.migrate.statement.to.enum=Unable to migrate statement to enum constant.{0}
codestyle.settings.extractor.command.name=CodeStyleSettings Extractor
+nullaway.suppress.command.name=Suppress NullAway Problem
+nullaway.suppress.inlay.text=Add @SuppressWarnings
+nullaway.suppress.error.no.target=Cannot find element to suppress
diff --git a/java/java-impl/src/com/intellij/java/impl/nullaway/NullAwayFilter.java b/java/java-impl/src/com/intellij/java/impl/nullaway/NullAwayFilter.java
new file mode 100644
index 000000000000..1cfff638d333
--- /dev/null
+++ b/java/java-impl/src/com/intellij/java/impl/nullaway/NullAwayFilter.java
@@ -0,0 +1,32 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.java.impl.nullaway;
+
+import com.intellij.execution.filters.Filter;
+import com.intellij.util.concurrency.annotations.RequiresReadLock;
+import org.jetbrains.annotations.NotNullByDefault;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/// Console filter that locates lines with [NullAway checker](https://github.com/uber/NullAway) warnings
+/// and adds `add @SuppressWarning` inlay button at the end of those lines.
+/// Each button action adds {@linkplain SuppressWarnings} annotation to the source code to silence relevant warning.
+/// The exact location where annotation is added is calculated based on information from the console line: file-path, line number,
+/// type of NullAway warning.
+@NotNullByDefault
+class NullAwayFilter implements Filter {
+ @Override
+ @RequiresReadLock
+ public @Nullable Result applyFilter(String line, int entireLength) {
+ var nullAwayProblem = NullAwayProblem.fromLogLine(line);
+ if (nullAwayProblem == null) return null;
+ var result = new Result(List.of(new NullAwayInlayProvider(entireLength - 1, entireLength - 1, nullAwayProblem)));
+ result.setNextAction(NextAction.CONTINUE_FILTERING);
+ return result;
+ }
+
+ @Override
+ public boolean isDumbAware() {
+ return true;
+ }
+}
diff --git a/java/java-impl/src/com/intellij/java/impl/nullaway/NullAwayFilterProvider.java b/java/java-impl/src/com/intellij/java/impl/nullaway/NullAwayFilterProvider.java
new file mode 100644
index 000000000000..a5d4cb50c72c
--- /dev/null
+++ b/java/java-impl/src/com/intellij/java/impl/nullaway/NullAwayFilterProvider.java
@@ -0,0 +1,51 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.java.impl.nullaway;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.execution.filters.ConsoleFilterProvider;
+import com.intellij.execution.filters.Filter;
+import com.intellij.java.library.JavaLibraryUtil;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNullByDefault;
+
+/// Provides [NullAwayFilter] when [NullAway checker](https://github.com/uber/NullAway) is configured in the project.
+@NotNullByDefault
+class NullAwayFilterProvider implements ConsoleFilterProvider {
+ @Override
+ public Filter[] getDefaultFilters(Project project) {
+ if (hasNullAwayPlugin(project)) {
+ return new Filter[]{new NullAwayFilter()};
+ }
+ else {
+ return Filter.EMPTY_ARRAY;
+ }
+ }
+
+ /// Returns true when NullAway plugin is configured.
+ /// Checks whether NullAway library is added to the project or the annotation processor path contains NullAway library.
+ /// Note:
+ /// - In maven projects NullAway library is not added to libraries even when null-away is configured as a plugin
+ /// for error-prone annotation processor in a maven build.
+ /// - In bazel projects NullAway library is not added to the annotation processor path even when NullAway is configured as a plugin
+ /// for error-prone annotation processor in a bazel build.
+ ///
+ /// @return true when NullAway plugin is configured.
+ private static boolean hasNullAwayPlugin(Project project) {
+ var hasNullAwayLib = ReadAction.compute(() -> JavaLibraryUtil.hasLibraryJar(project, "com.uber.nullaway:nullaway"));
+ if (hasNullAwayLib) return true;
+
+ var compilerConfiguration = CompilerConfiguration.getInstance(project);
+ if (!compilerConfiguration.isAnnotationProcessorsEnabled()) return false;
+ for (var module : ModuleManager.getInstance(project).getModules()) {
+ var config = compilerConfiguration.getAnnotationProcessingConfiguration(module);
+ if (config.isEnabled()) {
+ if (config.getProcessorPath().contains("nullaway")) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/java/java-impl/src/com/intellij/java/impl/nullaway/NullAwayInlayProvider.java b/java/java-impl/src/com/intellij/java/impl/nullaway/NullAwayInlayProvider.java
new file mode 100644
index 000000000000..b61467732855
--- /dev/null
+++ b/java/java-impl/src/com/intellij/java/impl/nullaway/NullAwayInlayProvider.java
@@ -0,0 +1,113 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.java.impl.nullaway;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.codeInsight.hints.presentation.InlayButtonPresentationFactory;
+import com.intellij.codeInsight.hints.presentation.PresentationFactory;
+import com.intellij.codeInsight.hints.presentation.PresentationRenderer;
+import com.intellij.codeInspection.JavaSuppressionUtil;
+import com.intellij.execution.filters.Filter;
+import com.intellij.execution.impl.InlayProvider;
+import com.intellij.java.JavaPluginDisposable;
+import com.intellij.modcommand.ActionContext;
+import com.intellij.modcommand.ModCommand;
+import com.intellij.modcommand.ModCommandExecutor;
+import com.intellij.modcommand.ModNavigate;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.editor.DefaultLanguageHighlighterColors;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.EditorCustomElementRenderer;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.project.Project;
+import com.intellij.pom.java.JavaFeature;
+import com.intellij.psi.PsiAnnotation;
+import com.intellij.psi.PsiModifierList;
+import com.intellij.psi.PsiModifierListOwner;
+import com.intellij.psi.util.PsiUtil;
+import com.intellij.refactoring.RefactorJBundle;
+import com.intellij.util.concurrency.AppExecutorUtil;
+import com.intellij.util.concurrency.annotations.RequiresReadLock;
+import org.jetbrains.annotations.NotNullByDefault;
+import org.jetbrains.annotations.Nullable;
+
+import static com.intellij.codeInspection.JavaSuppressionUtil.SUPPRESS_INSPECTIONS_ANNOTATION_NAME;
+
+@NotNullByDefault
+class NullAwayInlayProvider extends Filter.ResultItem implements InlayProvider {
+ private final NullAwayProblem problem;
+
+ NullAwayInlayProvider(int startOffset, int endOffset, NullAwayProblem problem) {
+ super(startOffset, endOffset, null);
+ this.problem = problem;
+ }
+
+ @Override
+ @RequiresReadLock
+ public EditorCustomElementRenderer createInlayRenderer(Editor editor) {
+ var factory = new InlayButtonPresentationFactory(
+ editor,
+ new PresentationFactory(editor),
+ DefaultLanguageHighlighterColors.INLAY_BUTTON_DEFAULT,
+ DefaultLanguageHighlighterColors.INLAY_BUTTON_HOVERED,
+ DefaultLanguageHighlighterColors.INLAY_BUTTON_FOCUSED);
+ var inlayPresentation = factory.smallText(RefactorJBundle.message("nullaway.suppress.inlay.text"))
+ .onClick((event, point) -> scheduleFixAction(editor, problem))
+ .build();
+ return new PresentationRenderer(inlayPresentation);
+ }
+
+ private static void scheduleFixAction(Editor editor, NullAwayProblem problem) {
+ Project project = editor.getProject();
+ if (project == null) return;
+ ReadAction.nonBlocking(() -> findSuppressionTarget(project, problem))
+ .expireWith(JavaPluginDisposable.getInstance(project))
+ .finishOnUiThread(ModalityState.current(), target -> applyFix(editor, problem, target))
+ .submit(AppExecutorUtil.getAppExecutorService());
+ }
+
+ private static @Nullable PsiModifierListOwner findSuppressionTarget(Project project, NullAwayProblem problem) {
+ var target = problem.findSuppressionTarget(project);
+ if (target == null) return null;
+ if (!PsiUtil.isAvailable(JavaFeature.ANNOTATIONS, target)) return null;
+ return target;
+ }
+
+ private static void applyFix(Editor editor, NullAwayProblem problem, @Nullable PsiModifierListOwner target) {
+ if (target == null) {
+ if (!editor.isDisposed()) {
+ HintManager.getInstance().showErrorHint(editor, RefactorJBundle.message("nullaway.suppress.error.no.target"));
+ }
+ }
+ else {
+ addSuppression(target, problem.kind().nameToSuppress());
+ }
+ }
+
+ private static void addSuppression(PsiModifierListOwner modifierListOwner, String namedToSuppress) {
+ ModCommandExecutor.executeInteractively(
+ ActionContext.from(null, modifierListOwner.getContainingFile()),
+ RefactorJBundle.message("nullaway.suppress.command.name"),
+ null,
+ () -> createNavigateCommand(modifierListOwner).andThen(createSuppressAndHighlightCommand(modifierListOwner, namedToSuppress)));
+ }
+
+ private static ModNavigate createNavigateCommand(PsiModifierListOwner modifierListOwner) {
+ return new ModNavigate(modifierListOwner.getContainingFile().getVirtualFile(), -1, -1,
+ modifierListOwner.getTextRange().getStartOffset());
+ }
+
+ private static ModCommand createSuppressAndHighlightCommand(PsiModifierListOwner modifierListOwner, String namedToSuppress) {
+ return ModCommand.psiUpdate(modifierListOwner, (e, updater) -> {
+ Project project = e.getProject();
+ DumbService.getInstance(project).withAlternativeResolveEnabled(() -> {
+ JavaSuppressionUtil.addSuppressAnnotation(project, e, e, namedToSuppress);
+ PsiModifierList modifierList = e.getModifierList();
+ if (modifierList == null) return;
+ PsiAnnotation annotation = modifierList.findAnnotation(SUPPRESS_INSPECTIONS_ANNOTATION_NAME);
+ if (annotation == null) return;
+ updater.highlight(annotation);
+ });
+ });
+ }
+}
diff --git a/java/java-impl/src/com/intellij/java/impl/nullaway/NullAwayProblem.java b/java/java-impl/src/com/intellij/java/impl/nullaway/NullAwayProblem.java
new file mode 100644
index 000000000000..141dd8c931b9
--- /dev/null
+++ b/java/java-impl/src/com/intellij/java/impl/nullaway/NullAwayProblem.java
@@ -0,0 +1,379 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.java.impl.nullaway;
+
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.psi.*;
+import com.intellij.psi.util.PsiTreeUtil;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNullByDefault;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.VisibleForTesting;
+
+import java.nio.file.Path;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static com.intellij.java.impl.nullaway.NullAwayProblem.Kind.TargetKind.FIELD;
+import static com.intellij.java.impl.nullaway.NullAwayProblem.Kind.TargetKind.METHOD;
+
+
+/// NullAway problem reported in the console.
+///
+/// @param filePath path to the file containing the problem
+/// @param lineNumber zero-based line number containing the problem
+/// @param kind type of the problem
+@NotNullByDefault
+@VisibleForTesting
+public record NullAwayProblem(Path filePath, int lineNumber, Kind kind) {
+ private static final Pattern NULLAWAY_LOG_LINE_PATTERN =
+ Pattern.compile(
+ "^(\\[(?:ERROR|WARNING|INFO)] )?(?.+?):[(\\[]?(?\\d+)([:,](?\\d++))?[)\\]]?(?:: | )(?:(?:error|warning): )?\\[NullAway] (?.+)");
+
+ @VisibleForTesting
+ public static @Nullable NullAwayProblem fromLogLine(String line) {
+ if (!line.contains("[NullAway]")) return null;
+ Matcher matcher = NULLAWAY_LOG_LINE_PATTERN.matcher(line);
+ if (!matcher.find()) return null;
+ String path = matcher.group("path");
+ Kind kind = Kind.fromMessage(matcher.group("message"));
+ if (kind == null) return null;
+ try {
+ int oneBasedLineNumber = Integer.parseInt(matcher.group("line"));
+ return new NullAwayProblem(Path.of(path), oneBasedLineNumber - 1, kind);
+ }
+ catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ @VisibleForTesting
+ public @Nullable PsiModifierListOwner findSuppressionTarget(Project project) {
+ var psiJavaFile = findFileWithProblem(project);
+ if (psiJavaFile == null) return null;
+ return findFaultyElement(psiJavaFile);
+ }
+
+ private @Nullable PsiJavaFile findFileWithProblem(Project project) {
+ Path path = resolvedPath(project);
+ if (path == null) return null;
+ var virtualFile = LocalFileSystem.getInstance().findFileByNioFile(path);
+ if (virtualFile == null) return null;
+ var psiFile = PsiManager.getInstance(project).findFile(virtualFile);
+ if (!(psiFile instanceof PsiJavaFile psiJavaFile) || psiFile instanceof PsiCompiledElement) return null;
+ return psiJavaFile;
+ }
+
+ private @Nullable Path resolvedPath(Project project) {
+ if (filePath.isAbsolute()) return filePath;
+ String basePath = project.getBasePath();
+ if (basePath == null) return null;
+ return Path.of(basePath).resolve(filePath);
+ }
+
+ @Nullable
+ private PsiModifierListOwner findFaultyElement(PsiJavaFile file) {
+ Project project = file.getProject();
+ Document document = PsiDocumentManager.getInstance(project).getDocument(file);
+ if (document == null) return null;
+
+ int lineIndex = Math.max(0, lineNumber);
+ if (lineIndex >= document.getLineCount()) return null;
+ int lineStartOffset = document.getLineStartOffset(lineIndex);
+ int lineEndOffset = document.getLineEndOffset(lineIndex);
+ var lineRange = TextRange.create(lineStartOffset, lineEndOffset);
+
+ PsiElement atLineStart = file.findElementAt(lineStartOffset);
+ PsiClass psiClass = PsiTreeUtil.getParentOfType(atLineStart, PsiClass.class, false);
+ if (psiClass == null) return null;
+ if (kind.targetKinds().contains(FIELD)) {
+ PsiField[] fields = psiClass.getFields();
+ var field = findElementInLine(fields, lineRange);
+ if (field != null) return field;
+ }
+ if (kind.targetKinds().contains(METHOD)) {
+ var method = findElementInLine(psiClass.getMethods(), lineRange);
+ if (method != null) return method;
+ method = findElementInLine(psiClass.getConstructors(), lineRange);
+ if (method != null) return method;
+
+ PsiClassInitializer classInitializer = PsiTreeUtil.getParentOfType(atLineStart, PsiClassInitializer.class, false);
+ if (classInitializer != null && PsiTreeUtil.getParentOfType(classInitializer, PsiClass.class, false) == psiClass) {
+ return psiClass;
+ }
+ }
+ return null;
+ }
+
+ private static @Nullable T findElementInLine(T[] psiElements,
+ TextRange lineRange) {
+ for (T psiElement : psiElements) {
+ if (lineRange.intersects(psiElement.getTextRange())) {
+ return psiElement;
+ }
+ }
+ return null;
+ }
+
+ @ApiStatus.Internal
+ public record Kind(Predicate matchingPredicate, String nameToSuppress, EnumSet targetKinds) {
+
+ Kind(String matchingRegex, String nameToSuppress, TargetKind elementType, TargetKind... rest) {
+ this(Pattern.compile(matchingRegex).asMatchPredicate(), nameToSuppress, EnumSet.of(elementType, rest));
+ }
+
+ public enum TargetKind {
+ METHOD, FIELD
+ }
+
+ public static final Kind NON_NULL_FIELD_NOT_INITIALIZED = new Kind(
+ "^@NonNull field (.+) not initialized.*",
+ "NullAway.Init",
+ FIELD
+ );
+
+ public static final Kind INITIALIZER_DOES_NOT_GUARANTEE_INITIALIZATION = new Kind(
+ "^initializer method does not guarantee @NonNull field (.+) \\(line \\d+\\) is initialized along all control-flow paths.*",
+ "NullAway.Init",
+ METHOD
+ );
+
+ public static final Kind DEREFERENCED_EXPRESSION_IS_NULLABLE = new Kind(
+ "^dereferenced expression .* is @Nullable.*",
+ "NullAway",
+ METHOD,
+ FIELD
+ );
+
+ public static final Kind RETURNING_NULLABLE_FROM_NONNULL_METHOD = new Kind(
+ "^returning @Nullable expression from method with @NonNull return type.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind PASSING_NULLABLE_PARAMETER_WHERE_NONNULL_REQUIRED = new Kind(
+ "^passing @Nullable parameter '.+' where @NonNull is required.*",
+ "NullAway",
+ METHOD,
+ FIELD
+ );
+
+ public static final Kind METHOD_RETURNS_NULLABLE_BUT_SUPERCLASS_METHOD_NONNULL = new Kind(
+ "^method returns @Nullable, but superclass method .* returns @NonNull.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind ASSIGNING_NULLABLE_TO_NONNULL_FIELD = new Kind(
+ "^assigning @Nullable expression to @NonNull field.*",
+ "NullAway",
+ METHOD,
+ FIELD
+ );
+
+ public static final Kind REFERENCED_METHOD_RETURNS_NULLABLE = new Kind(
+ "^referenced method returns @Nullable, but functional interface method .* returns @NonNull.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind UNBOUND_INSTANCE_METHOD_REFERENCE_FIRST_PARAMETER_NULLABLE = new Kind(
+ "^unbound instance method reference cannot be used, as first parameter .* is @Nullable.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind PARAMETER_IS_NONNULL_BUT_PARAMETER_IN_SUPERCLASS_NULLABLE = new Kind(
+ "^parameter .* is @NonNull, but parameter in .* is @Nullable.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind UNBOXING_OF_NULLABLE_VALUE = new Kind(
+ "^unboxing of a @Nullable value.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind READ_OF_NONNULL_FIELD_BEFORE_INIT = new Kind(
+ "^read of @NonNull field .* before initialization.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind ENHANCED_FOR_EXPRESSION_NULLABLE = new Kind(
+ "^enhanced-for expression .* is @Nullable.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind SYNCHRONIZED_BLOCK_EXPRESSION_NULLABLE = new Kind(
+ "^synchronized block expression \".+\" is @Nullable.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind NONNULL_STATIC_FIELD_NOT_INITIALIZED = new Kind(
+ "^@NonNull static field .* not initialized.*",
+ "NullAway.Init",
+ FIELD
+ );
+
+ public static final Kind PASSING_NONNULL_TO_CAST_TO_NONNULL = new Kind(
+ "^passing known @NonNull parameter '.+' to CastToNonNullMethod \\(.*\\) .*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind INVOKING_GET_ON_EMPTY_OPTIONAL = new Kind(
+ "^Invoking get\\(\\) on possibly empty Optional .*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind SWITCH_EXPRESSION_NULLABLE = new Kind(
+ "^switch expression .* is @Nullable.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind METHOD_ANNOTATED_WITH_ENSURES_NONNULL_BUT_FAILS = new Kind(
+ "^Method is annotated with @EnsuresNonNull but fails to ensure .*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind METHOD_ANNOTATED_WITH_ENSURES_NONNULL_IF_BUT_DOES_NOT_ENSURE = new Kind(
+ "^Method is annotated with @EnsuresNonNullIf but does not ensure fields .*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind EXPECTED_STATIC_FIELD_NONNULL_DUE_TO_REQUIRES_NONNULL = new Kind(
+ "^Expected static field .* to be non-null at call site due to @RequiresNonNull annotation on invoked method.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind EXPECTED_FIELD_NONNULL_DUE_TO_REQUIRES_NONNULL = new Kind(
+ "^Expected field .* to be non-null at call site due to @RequiresNonNull annotation on invoked method.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind POSTCONDITION_INHERITANCE_VIOLATED = new Kind(
+ "^postcondition inheritance is violated, this method must guarantee that all fields .* are @NonNull .*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind PRECONDITION_INHERITANCE_VIOLATED = new Kind(
+ "^precondition inheritance is violated, method in child class cannot have a stricter precondition .*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind TYPE_ARGUMENT_CANNOT_BE_NULLABLE = new Kind(
+ "^Type argument cannot be @Nullable, as method .*'s type variable .* is not @Nullable.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind GENERIC_TYPE_PARAMETER_CANNOT_BE_NULLABLE = new Kind(
+ "^Generic type parameter cannot be @Nullable, as type variable .* of type .* does not have a @Nullable upper bound.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind INCOMPATIBLE_TYPES_GENERIC = new Kind(
+ "^incompatible types: .* cannot be converted to .*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind CONDITIONAL_EXPRESSION_TYPE_MISMATCH = new Kind(
+ "^Conditional expression must have type .* but the sub-expression has type .*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind METHOD_RETURNS_GENERIC_WITH_MISMATCHED_NULLABILITY = new Kind(
+ "^Method returns .*, but overridden method returns .*, which has mismatched type parameter nullability.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind PARAMETER_TYPE_GENERIC_WITH_MISMATCHED_NULLABILITY = new Kind(
+ "^Parameter has type .*, but overridden method has parameter type .*, which has mismatched type parameter nullability.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind WRITING_NULLABLE_INTO_NONNULL_ARRAY = new Kind(
+ "^Writing @Nullable expression into array with @NonNull contents.*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind FAILED_TO_INFER_TYPE_ARGUMENT_NULLABILITY = new Kind(
+ "^Failed to infer type argument nullability for call .*",
+ "NullAway",
+ METHOD
+ );
+
+ public static final Kind TYPE_USE_NULLABILITY_ON_WRONG_NESTED_CLASS_LEVEL = new Kind(
+ "^Type-use nullability annotations should be applied on inner class.*",
+ "NullAway",
+ METHOD
+ );
+
+ private static final List ALL_KINDS =
+ List.of(NON_NULL_FIELD_NOT_INITIALIZED,
+ INITIALIZER_DOES_NOT_GUARANTEE_INITIALIZATION,
+ DEREFERENCED_EXPRESSION_IS_NULLABLE,
+ RETURNING_NULLABLE_FROM_NONNULL_METHOD,
+ PASSING_NULLABLE_PARAMETER_WHERE_NONNULL_REQUIRED,
+ METHOD_RETURNS_NULLABLE_BUT_SUPERCLASS_METHOD_NONNULL,
+ ASSIGNING_NULLABLE_TO_NONNULL_FIELD,
+ REFERENCED_METHOD_RETURNS_NULLABLE,
+ UNBOUND_INSTANCE_METHOD_REFERENCE_FIRST_PARAMETER_NULLABLE,
+ PARAMETER_IS_NONNULL_BUT_PARAMETER_IN_SUPERCLASS_NULLABLE,
+ UNBOXING_OF_NULLABLE_VALUE,
+ READ_OF_NONNULL_FIELD_BEFORE_INIT,
+ ENHANCED_FOR_EXPRESSION_NULLABLE,
+ SYNCHRONIZED_BLOCK_EXPRESSION_NULLABLE,
+ NONNULL_STATIC_FIELD_NOT_INITIALIZED,
+ PASSING_NONNULL_TO_CAST_TO_NONNULL,
+ INVOKING_GET_ON_EMPTY_OPTIONAL,
+ SWITCH_EXPRESSION_NULLABLE,
+ METHOD_ANNOTATED_WITH_ENSURES_NONNULL_BUT_FAILS,
+ METHOD_ANNOTATED_WITH_ENSURES_NONNULL_IF_BUT_DOES_NOT_ENSURE,
+ EXPECTED_STATIC_FIELD_NONNULL_DUE_TO_REQUIRES_NONNULL,
+ EXPECTED_FIELD_NONNULL_DUE_TO_REQUIRES_NONNULL,
+ POSTCONDITION_INHERITANCE_VIOLATED,
+ PRECONDITION_INHERITANCE_VIOLATED,
+ TYPE_ARGUMENT_CANNOT_BE_NULLABLE,
+ GENERIC_TYPE_PARAMETER_CANNOT_BE_NULLABLE,
+ INCOMPATIBLE_TYPES_GENERIC,
+ CONDITIONAL_EXPRESSION_TYPE_MISMATCH,
+ METHOD_RETURNS_GENERIC_WITH_MISMATCHED_NULLABILITY,
+ PARAMETER_TYPE_GENERIC_WITH_MISMATCHED_NULLABILITY,
+ WRITING_NULLABLE_INTO_NONNULL_ARRAY,
+ FAILED_TO_INFER_TYPE_ARGUMENT_NULLABILITY,
+ TYPE_USE_NULLABILITY_ON_WRONG_NESTED_CLASS_LEVEL);
+
+ private static @Nullable Kind fromMessage(String message) {
+ for (Kind kind : ALL_KINDS) {
+ if (kind.matchingPredicate.test(message)) {
+ return kind;
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git a/java/java-tests/testSrc/com/intellij/java/impl/nullaway/NullAwayProblemFindTargetTest.java b/java/java-tests/testSrc/com/intellij/java/impl/nullaway/NullAwayProblemFindTargetTest.java
new file mode 100644
index 000000000000..17a048b37c29
--- /dev/null
+++ b/java/java-tests/testSrc/com/intellij/java/impl/nullaway/NullAwayProblemFindTargetTest.java
@@ -0,0 +1,96 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.java.impl.nullaway;
+
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiField;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiMethod;
+import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
+import org.intellij.lang.annotations.Language;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.Assertions;
+
+import java.nio.file.Path;
+
+import static com.intellij.java.impl.nullaway.NullAwayProblem.Kind.ASSIGNING_NULLABLE_TO_NONNULL_FIELD;
+
+
+public class NullAwayProblemFindTargetTest extends JavaCodeInsightFixtureTestCase {
+ public void testFindFieldTarget() {
+ Path filePath = configureFile(
+ """
+ public class MyClass {
+ private String targetField;
+ }
+ """);
+
+ var nullAwayProblem = new NullAwayProblem(filePath, 1, ASSIGNING_NULLABLE_TO_NONNULL_FIELD);
+ assertFieldTargetFound(nullAwayProblem, "targetField");
+ }
+
+ public void testFindMethodTarget() {
+ Path filePath = configureFile(
+ """
+ public class MyClass {
+ public String someMethod(String s) {
+ return s;
+ }
+ }
+ """);
+
+ var nullAwayProblem = new NullAwayProblem(filePath, 2, ASSIGNING_NULLABLE_TO_NONNULL_FIELD);
+ assertMethodTargetFound(nullAwayProblem, "someMethod");
+ }
+
+ public void testFindClassTargetForStaticInitializer() {
+ Path filePath = configureFile(
+ """
+ public class MyClass {
+ String nullable = null;
+ @NonNull nonNull;
+ static {
+ nonNull = nullable;
+ }
+ }
+ """);
+
+ var nullAwayProblem = new NullAwayProblem(filePath, 4, ASSIGNING_NULLABLE_TO_NONNULL_FIELD);
+ assertClassTargetFound(nullAwayProblem, "MyClass");
+ }
+
+ private @Nullable Path configureFile(@Language("JAVA") String text) {
+ PsiFile file = myFixture.configureByText("MyClass.java", text);
+ String canonicalPath = file.getVirtualFile().getCanonicalPath();
+ return canonicalPath != null ? Path.of(canonicalPath) : null;
+ }
+
+ private void assertFieldTargetFound(NullAwayProblem nullAwayProblem, String fieldName) {
+ var target = nullAwayProblem.findSuppressionTarget(getProject());
+ if (target instanceof PsiField psiField) {
+ assertEquals(fieldName, psiField.getName());
+ }
+ else {
+ Assertions.fail("Expected target to be of PsiField type but got " + (target == null ? "null" : target.getClass().getName()));
+ }
+ }
+
+ private void assertMethodTargetFound(NullAwayProblem nullAwayProblem, String methodName) {
+ var target = nullAwayProblem.findSuppressionTarget(getProject());
+ if (target instanceof PsiMethod psiMethod) {
+ assertEquals(methodName, psiMethod.getName());
+ }
+ else {
+ Assertions.fail("Expected target to be of PsiMethod type but got " + (target == null ? "null" : target.getClass().getName()));
+ }
+ }
+
+ private void assertClassTargetFound(NullAwayProblem nullAwayProblem, String className) {
+ var target = nullAwayProblem.findSuppressionTarget(getProject());
+ if (target instanceof PsiClass psiClass) {
+ assertEquals(className, psiClass.getName());
+ }
+ else {
+ Assertions.fail("Expected target to be of PsiClass type but got " + (target == null ? "null" : target.getClass().getName()));
+ }
+ }
+}
diff --git a/java/java-tests/testSrc/com/intellij/java/impl/nullaway/NullAwayProblemTest.java b/java/java-tests/testSrc/com/intellij/java/impl/nullaway/NullAwayProblemTest.java
new file mode 100644
index 000000000000..73ddbe473313
--- /dev/null
+++ b/java/java-tests/testSrc/com/intellij/java/impl/nullaway/NullAwayProblemTest.java
@@ -0,0 +1,171 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.java.impl.nullaway;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.nio.file.Path;
+import java.util.List;
+
+import static com.intellij.java.impl.nullaway.NullAwayProblem.Kind.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class NullAwayProblemTest {
+
+ @ParameterizedTest
+ @MethodSource
+ void filePathParsedCorrectly(String logLine, String expectedFilePath) {
+ NullAwayProblem problem = NullAwayProblem.fromLogLine(logLine);
+ assertEquals(Path.of(expectedFilePath), problem.filePath());
+ }
+
+ public static List filePathParsedCorrectly() {
+ return List.of(
+ // Gradle format
+ Arguments.of(
+ "/full/path/MyClass.java:10: error: [NullAway] @NonNull field myValue not initialized",
+ "/full/path/MyClass.java"),
+ Arguments.of(
+ "/full/path/AClass.java:11: warning: [NullAway] @NonNull field someValue not initialized",
+ "/full/path/AClass.java"),
+ // Maven format
+ Arguments.of(
+ "/full/path/MyClass.java:[100,11] [NullAway] @NonNull field value3 not initialized",
+ "/full/path/MyClass.java"),
+ Arguments.of(
+ "[WARNING] /full/path/AClass.java:[102,11] [NullAway] @NonNull field valueX not initialized",
+ "/full/path/AClass.java")
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void lineNumberParsedCorrectly(String logLine, int expectedLineNumber) {
+ NullAwayProblem problem = NullAwayProblem.fromLogLine(logLine);
+ assertEquals(expectedLineNumber, problem.lineNumber());
+ }
+
+ public static List lineNumberParsedCorrectly() {
+ return List.of(
+ // gradle
+ Arguments.of("/full/path/MyClass.java:10: error: [NullAway] @NonNull field myValue not initialized", 9),
+ Arguments.of("/full/path/MyClass.java:10:12: error: [NullAway] @NonNull field myValue not initialized", 9),
+ // maven
+ Arguments.of("/full/path/MyClass.java:[100,11] [NullAway] @NonNull field value3 not initialized", 99),
+ Arguments.of("/full/path/MyClass.java:[101] [NullAway] @NonNull field value3 not initialized", 100),
+ Arguments.of("[WARNING] /full/path/AClass.java:[102,11] [NullAway] @NonNull field valueX not initialized", 101)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void kindMatchedCorrectly(String logLine, NullAwayProblem.Kind expectedKind) {
+ NullAwayProblem problem = NullAwayProblem.fromLogLine(logLine);
+ assertEquals(expectedKind, problem.kind());
+ }
+
+ public static List kindMatchedCorrectly() {
+ return List.of(
+ Arguments.of(
+ "/full/path/MyClass.java:10: error: [NullAway] @NonNull field myValue not initialized",
+ NON_NULL_FIELD_NOT_INITIALIZED),
+ Arguments.of(
+ "/full/path/MyClass.java:20: error: [NullAway] initializer method does not guarantee @NonNull field fieldA (line 33) is initialized along all control-flow paths (remember to check for exceptions or early returns).",
+ INITIALIZER_DOES_NOT_GUARANTEE_INITIALIZATION),
+ Arguments.of(
+ "/full/path/MyClass.java:30: error: [NullAway] dereferenced expression data is @Nullable",
+ DEREFERENCED_EXPRESSION_IS_NULLABLE),
+ Arguments.of(
+ "/full/path/MyClass.java:40: error: [NullAway] returning @Nullable expression from method with @NonNull return type",
+ RETURNING_NULLABLE_FROM_NONNULL_METHOD),
+ Arguments.of(
+ "/full/path/MyClass.java:50: error: [NullAway] passing @Nullable parameter 'foo' where @NonNull is required",
+ PASSING_NULLABLE_PARAMETER_WHERE_NONNULL_REQUIRED),
+ Arguments.of(
+ "/full/path/MyClass.java:60: error: [NullAway] method returns @Nullable, but superclass method org.testcases.SuperClass.getstring() returns @NonNull",
+ METHOD_RETURNS_NULLABLE_BUT_SUPERCLASS_METHOD_NONNULL),
+ Arguments.of(
+ "/full/path/MyClass.java:70: error: [NullAway] assigning @Nullable expression to @NonNull field",
+ ASSIGNING_NULLABLE_TO_NONNULL_FIELD),
+ Arguments.of(
+ "/full/path/MyClass.java:80: error: [NullAway] referenced method returns @Nullable, but functional interface method someMethod() returns @NonNull",
+ REFERENCED_METHOD_RETURNS_NULLABLE),
+ Arguments.of(
+ "/full/path/MyClass.java:90: error: [NullAway] unbound instance method reference cannot be used, as first parameter param1 is @Nullable",
+ UNBOUND_INSTANCE_METHOD_REFERENCE_FIRST_PARAMETER_NULLABLE),
+ Arguments.of(
+ "/full/path/MyClass.java:100: error: [NullAway] parameter param1 is @NonNull, but parameter in superMethod is @Nullable",
+ PARAMETER_IS_NONNULL_BUT_PARAMETER_IN_SUPERCLASS_NULLABLE),
+ Arguments.of(
+ "/full/path/MyClass.java:110: error: [NullAway] unboxing of a @Nullable value",
+ UNBOXING_OF_NULLABLE_VALUE),
+ Arguments.of(
+ "/full/path/MyClass.java:120: error: [NullAway] read of @NonNull field myField before initialization",
+ READ_OF_NONNULL_FIELD_BEFORE_INIT),
+ Arguments.of(
+ "/full/path/MyClass.java:130: error: [NullAway] enhanced-for expression items is @Nullable",
+ ENHANCED_FOR_EXPRESSION_NULLABLE),
+ Arguments.of(
+ "/full/path/MyClass.java:140: error: [NullAway] synchronized block expression \"lock\" is @Nullable",
+ SYNCHRONIZED_BLOCK_EXPRESSION_NULLABLE),
+ Arguments.of(
+ "/full/path/MyClass.java:150: error: [NullAway] @NonNull static field myStaticField not initialized",
+ NONNULL_STATIC_FIELD_NOT_INITIALIZED),
+ Arguments.of(
+ "/full/path/MyClass.java:160: error: [NullAway] passing known @NonNull parameter 'value' to CastToNonNullMethod (com.example.Util.cast) is unnecessary",
+ PASSING_NONNULL_TO_CAST_TO_NONNULL),
+ Arguments.of(
+ "/full/path/MyClass.java:170: error: [NullAway] Invoking get() on possibly empty Optional myOptional",
+ INVOKING_GET_ON_EMPTY_OPTIONAL),
+ Arguments.of(
+ "/full/path/MyClass.java:180: error: [NullAway] switch expression result is @Nullable",
+ SWITCH_EXPRESSION_NULLABLE),
+ Arguments.of(
+ "/full/path/MyClass.java:190: error: [NullAway] Method is annotated with @EnsuresNonNull but fails to ensure field myField is non-null",
+ METHOD_ANNOTATED_WITH_ENSURES_NONNULL_BUT_FAILS),
+ Arguments.of(
+ "/full/path/MyClass.java:200: error: [NullAway] Method is annotated with @EnsuresNonNullIf but does not ensure fields myField are non-null",
+ METHOD_ANNOTATED_WITH_ENSURES_NONNULL_IF_BUT_DOES_NOT_ENSURE),
+ Arguments.of(
+ "/full/path/MyClass.java:210: error: [NullAway] Expected static field myStaticField to be non-null at call site due to @RequiresNonNull annotation on invoked method",
+ EXPECTED_STATIC_FIELD_NONNULL_DUE_TO_REQUIRES_NONNULL),
+ Arguments.of(
+ "/full/path/MyClass.java:220: error: [NullAway] Expected field myField to be non-null at call site due to @RequiresNonNull annotation on invoked method",
+ EXPECTED_FIELD_NONNULL_DUE_TO_REQUIRES_NONNULL),
+ Arguments.of(
+ "/full/path/MyClass.java:230: error: [NullAway] postcondition inheritance is violated, this method must guarantee that all fields myField are @NonNull after invocation",
+ POSTCONDITION_INHERITANCE_VIOLATED),
+ Arguments.of(
+ "/full/path/MyClass.java:240: error: [NullAway] precondition inheritance is violated, method in child class cannot have a stricter precondition than parent",
+ PRECONDITION_INHERITANCE_VIOLATED),
+ Arguments.of(
+ "/full/path/MyClass.java:250: error: [NullAway] Type argument cannot be @Nullable, as method someMethod's type variable T is not @Nullable",
+ TYPE_ARGUMENT_CANNOT_BE_NULLABLE),
+ Arguments.of(
+ "/full/path/MyClass.java:260: error: [NullAway] Generic type parameter cannot be @Nullable, as type variable T of type MyClass does not have a @Nullable upper bound",
+ GENERIC_TYPE_PARAMETER_CANNOT_BE_NULLABLE),
+ Arguments.of(
+ "/full/path/MyClass.java:270: error: [NullAway] incompatible types: List<@Nullable String> cannot be converted to List",
+ INCOMPATIBLE_TYPES_GENERIC),
+ Arguments.of(
+ "/full/path/MyClass.java:280: error: [NullAway] Conditional expression must have type List but the sub-expression has type List<@Nullable String>",
+ CONDITIONAL_EXPRESSION_TYPE_MISMATCH),
+ Arguments.of(
+ "/full/path/MyClass.java:290: error: [NullAway] Method returns List<@Nullable String>, but overridden method returns List, which has mismatched type parameter nullability",
+ METHOD_RETURNS_GENERIC_WITH_MISMATCHED_NULLABILITY),
+ Arguments.of(
+ "/full/path/MyClass.java:300: error: [NullAway] Parameter has type List, but overridden method has parameter type List<@Nullable String>, which has mismatched type parameter nullability",
+ PARAMETER_TYPE_GENERIC_WITH_MISMATCHED_NULLABILITY),
+ Arguments.of(
+ "/full/path/MyClass.java:310: error: [NullAway] Writing @Nullable expression into array with @NonNull contents.",
+ WRITING_NULLABLE_INTO_NONNULL_ARRAY),
+ Arguments.of(
+ "/full/path/MyClass.java:320: error: [NullAway] Failed to infer type argument nullability for call someMethod()",
+ FAILED_TO_INFER_TYPE_ARGUMENT_NULLABILITY),
+ Arguments.of(
+ "/full/path/MyClass.java:330: error: [NullAway] Type-use nullability annotations should be applied on inner class",
+ TYPE_USE_NULLABILITY_ON_WRONG_NESTED_CLASS_LEVEL)
+ );
+ }
+}