IntelliJ IDEA Inspection Engine: Local PSI Analysis Architecture Note
IntelliJ keeps inspections safe by making them stateless, file-local PSI visitors with timeout isolation and read-only access, and only allowing changes via vetted quick-fix commands.
07 Jan 2026, 07:25 UTC

The problem with inspections that touch too much
Adding a custom code inspection that freezes the editor, leaks memory, or crashes on a large file is a common failure mode. The useful takeaway is that IntelliJ IDEA keeps inspections intentionally small: each inspection is a stateless, read-only visitor over a single file's PSI tree, runs off the UI thread with a timeout, and can only change the project through vetted quick-fix actions. That constraint is the reason inspections can be real-time and extensible without destabilizing the IDE.
Requirements for real-time, non-blocking analysis
Inspections must detect issues as you type, suggest quick fixes, and remain extensible by plugins for language-specific rules. That requires asynchronous execution so the UI does not block, and a stable extension point so third-party authors can add rules without modifying core code.
PSI, the Program Structure Interface, is IntelliJ's read-only in-memory representation of source code. Inspections operate on PSI elements rather than raw text, which gives structural navigation but also limits scope to what is already indexed.
Smallest suitable design: LocalInspectionTool per file
The minimal design is a LocalInspectionTool implementation that is stateless and file-local. An inspection receives a PSI file, visits relevant nodes, and returns InspectionProblemDescriptor objects. It holds no mutable state between runs.
Registration is declarative in plugin.xml. A typical entry declares language, display name, and description. The IDE discovers the extension point com.intellij.localInspection and instantiates the tool per file.
<extension point="com.intellij.localInspection">
<localInspection language="JAVA"
implementation="com.example.NullDereferenceInspection"
displayName="Null pointer dereference"
enabledByDefault="true" />
</extension>This design keeps the inspection scope to one file. Cross-file analysis is deliberately not supported here.
Trust and data boundaries
Inspections receive read-only PSI elements. They must not perform arbitrary file I/O or network calls. Modification of the project is only allowed via quick-fix actions that are executed through the IDE's command processor, which wraps changes in a write action and undoable transaction.
Quick-fixes that touch multiple files must respect the transaction model. Otherwise the project can be left in an inconsistent state with partial writes.
Plugin inspections run with the same process privileges as the IDE, so the boundary is enforced by API design rather than sandboxing. Third-party plugins that rely on internal APIs can break after an IDE upgrade.
Operational checks and observability
The IDE isolates inspections in a separate thread pool and watches execution time. If an inspection exceeds a configurable timeout, it is aborted and reported as slow in the Inspection Results tool window.
Operational verification can be done without code changes:
- Settings → Editor → Inspections → Java → Probable bugs → Null-pointer dereference. Enable the inspection, open a Java file with a known nullable dereference, and confirm a warning and quick-fix are offered.
- Analyze → Inspect Code on a module. In the results view, check that the inspection's execution time stays below the profile timeout.
- Disable the inspection, rebuild, and verify the warning disappears; re-enable and verify it returns.
Running many heavyweight inspections simultaneously increases memory usage and can cause UI lag on large projects.
Failure modes and safe degradation
An unchecked exception inside an inspection can corrupt the inspection session. The engine falls back to a safe mode for that run to avoid crashing the editor.
False positives and negatives arise when PSI is incomplete, e.g., during incomplete parsing or after a large refactor before indexing finishes. Because inspections are file-local, they cannot resolve references that require whole-project analysis.
When the design would need to change
The local, stateless model is sufficient for syntactic and intra-procedural checks. It would need to change if requirements demand cross-file analysis, incremental indexing-aware analysis, or tight integration with external static analysis tools that need persistent state.
Those conditions introduce new trust boundaries, longer execution times, and coordination with the indexing subsystem, which is why IntelliJ provides separate APIs for global inspections and external tool integrations rather than extending LocalInspectionTool.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.