Whether you’re just getting started or already publishing on JetBrains Marketplace, you’ll find new ideas, practical techniques, and stories from plugin developers who’ve been there.
The talks on this year’s agenda include:
You can choose whether to attend just a few individual sessions or watch every one of them. We hope you enjoy the talks, and we encourage you to ask questions!
We’ll stream the presentations live on YouTube, and all the sessions will remain available after the event is over so you can catch up on any you missed.
]]>Starting with version 2025.2.3, JetBrains IDEs will offer a new look: the Islands theme, available in both dark and light modes. This update is a visual refresh, which means that all functionality will remain the same. The new theme can be turned on or off in the Appearance settings.
This step brings us closer to our goal of making the Islands theme the default in the future, and on this mission, we’re taking an iterative and feedback-driven approach. We’re making small, focused improvements and sharing them early, with your feedback guiding our next steps.
With this update to the UI, we want to:
During the 2025.2 EAP, we tested two design directions, and based on your feedback, we decided to move forward with what is now the Islands theme.
To guide our decision, we combined results from mockup-based surveys, in-product A/B tests, user interviews, and usage statistics. This mix of qualitative and quantitative research helped us understand how each option worked in practice.
In the surveys and interviews, participants described the Islands theme as modern, clear, and easy to navigate, with better visual separation between the editor and tool windows. Many also appreciated the improved visibility of tabs and navigation elements. Overall, users were enthusiastic and ready to adopt the Islands theme as the default.
Here are some examples of the feedback we received:
Your feedback also identified lots of things we can still improve on, and we’ll be working on them for the future releases.
We’re grateful to everyone who tried both new themes in those early stages and shared feedback. All of your responses, both supportive and critical, helped us make a more confident and informed decision.
The Islands theme is now available for everyone to try. If you enable it, you’ll get a survey invitation two days later. Please take a moment to share your thoughts. Your feedback makes a huge difference in our work to refine the design further.
As a thank-you, survey participants will be entered into a raffle for a chance to win either an Amazon Gift Card or a 1-year All Products Pack subscription.
The Islands theme is in Beta, and we are still polishing some details and working to address known issues. Please vote and comment on the tickets that are most important to you, or submit a new issue if you notice something that isn’t on our radar.
]]>You have probably seen this picture occasionally and wondered what is happening with your IDE and why it freezes! This is a tricky question, and many types of bugs and performance issues may lead to UI freezes.
JetBrains IDEs are built on top of a UI framework (the Java AWT) that uses a single thread, the event dispatch thread (EDT), to perform painting operations and process user input events.
A UI freeze occurs when the IDE cannot execute operations on the EDT, preventing interaction with the IDE. This blog tries to consolidate our knowledge about freezing reasons and causes. Use it to investigate and resolve UI freezes in your IDE plugins!
Because our UI frameworks are built on top of a single thread, it is important to keep the EDT from remaining blocked for a long time. The EDT runs an event loop that processes AWT events. Typical examples of these events are user input (such as typing or moving the mouse) and repaint requests from Swing.
Events need to be processed within 16 ms. Otherwise, the IDE is not able to render 60 frames per second.
In the vast majority of cases, simply looking at the thread dump will give you the information you need about a UI freeze. The investigation should start with the thread AWT-EventQueue-N – this is the EDT. Normally, its name should be AWT-EventQueue-0. If you see a bigger number in the suffix, there might be a platform issue.
In the event of a UI freeze, the EDT is usually blocked on some kind of lock.
If you are unfamiliar with the concept of the IntelliJ Platform’s read-write lock, you can learn more about it from this notebook.
Due to the IntelliJ Platform’s architecture, the write lock is often acquired on the EDT. Since it often isn’t possible to acquire the write lock immediately, you will sometimes see the following lines in the stack trace of the EDT:
"AWT-EventQueue-0" prio=0 tid=0x0 nid=0x0 waiting on condition java.lang.Thread.State: TIMED_WAITING on com.intellij.openapi.progress.util.EternalEventStealer@3a946cba at java.base@21.0.8/java.lang.Object.wait0(Native Method) ... (!) at com.intellij.platform.locking.impl.NestedLocksThreadingSupport$ComputationState.upgradeWritePermit(NestedLocksThreadingSupport.kt:370) ... at com.intellij.platform.locking.impl.NestedLocksThreadingSupport.runWriteAction(NestedLocksThreadingSupport.kt:921) ... at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.kt:347) ... at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:92)
The key line here is marked with (!)
. This line indicates that the EDT is blocked on the acquisition of the write lock. In this case, there is no need to look above. The IntelliJ Platform usually enters emergency mode if it detects a UI freeze and runs some of its internal procedures.
The EDT being blocked on the write lock is a sign that a thread is running under the read lock. The next step in our investigation is to find this background thread. This can be done by searching for a substring readAction
(case-insensitive) in the thread dump.
For example, we may find the following thread:
"JobScheduler FJ pool 1/11" prio=0 tid=0x0 nid=0x0 runnable java.lang.Thread.State: RUNNABLE at ai.grazie.rules.en.QuantifierNounCompatibility.<clinit>(QuantifierNounCompatibility.java:38) at ai.grazie.rules.en.AgreementSet.<clinit>(AgreementSet.java:42) at ai.grazie.rules.en.PluralsInCompounds.<clinit>(PluralsInCompounds.java:21) at ai.grazie.rules.en.Articles.<clinit>(Articles.java:250) ... at ai.grazie.rules.toolkit.LanguageToolkit.allParameters(LanguageToolkit.java:87) (!!) at com.intellij.grazie.pro.TreeRuleChecker.calcParameters(TreeRuleChecker.java:234) ... at com.intellij.codeInsight.daemon.impl.AnnotatorRunner$$Lambda/0x00000070047e8000.run(Unknown Source) ... (!) at com.intellij.platform.locking.impl.NestedLocksThreadingSupport.tryRunReadAction(NestedLocksThreadingSupport.kt:826) ... at java.base@21.0.8/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)
This thread is taken from ForkJoinPool
, and we see that it is currently running a read action (at (!)). We also see AnnotatorRunner
in the stack trace, which means that this thread is currently running highlighting. The read actions in highlighting are canceled on a pending write action, which means that the UI freeze is caused by the code not checking for ProgressManager.checkCanceled
. In the middle of the block, we see some traces from the Grazie plugin (starting from (!!)
). This concludes our investigation. We’ve found the problem – Grazie does not check for checkCanceled
frequently enough, which causes the UI to freeze.
The IntelliJ Platform gradually moves write actions to background threads, but this functionality is currently unstable, and it can cause additional UI freezes and deadlocks.
While a write action can run in the background, the EDT still often acquires a write-intent lock, resulting in stack traces like the following:
"AWT-EventQueue-0" #91 [119043] prio=6 os_prio=31 cpu=71985.87ms elapsed=1065.05s tid=0x00000001610c4c00 nid=119043 sleeping [0x0000000398429000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep0(java.base@21.0.8/Native Method) ... at com.intellij.platform.locking.impl.NestedLocksThreadingSupport$ComputationState.acquireWriteIntentPermit(NestedLocksThreadingSupport.kt:416) ... at java.awt.EventDispatchThread.run(java.desktop/EventDispatchThread.java:92)
This means that the EDT cannot immediately acquire a write-intent lock. Our next step is to search for a write action. In this case, it may appear in coroutine dumps. For example:
- "RefreshQueue pool":StandaloneCoroutine{Active}, state: SUSPENDED [Kernel@lumrbnr1s12qelije7p5, Rete(abortOnError=false, commands=capacity=2147483647,data=[onReceive], reteState=kotlinx.coroutines.flow.StateFlowImpl@6d763516, dbSource=ReteDbSource(reteState=kotlinx.coroutines.flow.StateFlowImpl@6d763516)), DbSourceContextElement(kernel Kernel@lumrbnr1s12qelije7p5), ComponentManager(ApplicationImpl@106897812), com.intellij.codeWithMe.ClientIdContextElementPrecursor, Dispatchers.Default.limitedParallelism(1)] at com.intellij.core.rwmutex.WriteIntentPermitImpl.acquireWriteActionPermit(RWMutexIdea.kt:263) ... at com.intellij.openapi.vfs.newvfs.RefreshQueueImpl.processEventsSuspending(RefreshQueueImpl.kt:191) ... at com.intellij.openapi.vfs.newvfs.RefreshQueueImpl$queueAsyncSessionWithCoroutines$3$1.invokeSuspend(RefreshQueueImpl.kt:177)
Here we see that a coroutine is SUSPENDED
on acquisition of the write lock. In particular, VFS refresh cannot proceed, which means that a read action is alive. Indeed:
"JobScheduler FJ pool 7/9" #201 [175107] daemon prio=6 os_prio=31 cpu=153605.15ms elapsed=1039.57s tid=0x0000000142955a00 nid=175107 runnable [0x00000003610e7000] java.lang.Thread.State: RUNNABLE at com.intellij.psi.impl.file.impl.MultiverseFileViewProviderCache.get(MultiverseFileViewProviderCache.kt:67) ... at org.jetbrains.kotlin.idea.codeInsight.lineMarkers.KotlinRecursiveCallLineMarkerProvider.collectSlowLineMarkers(KotlinRecursiveCallLineMarkerProvider.kt:35) ... at com.intellij.openapi.application.impl.ApplicationImpl.tryRunReadAction(ApplicationImpl.java:1206) ... at java.util.concurrent.ForkJoinWorkerThread.run(java.base@21.0.8/ForkJoinWorkerThread.java:187)
Here we again arrive at the conclusion that some code cannot be canceled due to the read action. The process of resolving this freeze is similar to what we have discussed above.
SuvorovProgress
Sometimes the EDT is blocked in a class named SuvorovProgress
:
"AWT-EventQueue-0" prio=0 tid=0x0 nid=0x0 waiting on condition java.lang.Thread.State: TIMED_WAITING on com.intellij.openapi.progress.util.EternalEventStealer@215bb938 at java.base@21.0.7/java.lang.Object.wait0(Native Method) at java.base@21.0.7/java.lang.Object.wait(Object.java:366) at com.intellij.openapi.progress.util.EternalEventStealer.dispatchAllEventsForTimeout(SuvorovProgress.kt:261) at com.intellij.openapi.progress.util.SuvorovProgress.processInvocationEventsWithoutDialog(SuvorovProgress.kt:125) at com.intellij.openapi.progress.util.SuvorovProgress.dispatchEventsUntilComputationCompletes(SuvorovProgress.kt:73) at com.intellij.openapi.application.impl.ApplicationImpl.lambda$postInit$14(ApplicationImpl.java:1434) at com.intellij.openapi.application.impl.ApplicationImpl$$Lambda/0x000076ace059cff8.invoke(Unknown Source) at com.intellij.platform.locking.impl.RunSuspend.await(NestedLocksThreadingSupport.kt:1517) at com.intellij.platform.locking.impl.NestedLocksThreadingSupportKt.runSuspendWithWaitingConsumer(NestedLocksThreadingSupport.kt:1472) ... at com.intellij.platform.locking.impl.NestedLocksThreadingSupport.runWriteAction(NestedLocksThreadingSupport.kt:921) ... at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:105) at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:92)
SuvorovProgress
is merely a symptom of a UI freeze, however. It indicates that the IntelliJ Platform is in emergency mode, where it is still able to run certain trusted AWT events or draw a freeze popup UI.
It is very rarely a cause of a freeze, so the problem is likely somewhere else.
The IntelliJ Platform uses the Kotlin Coroutines library a lot. In coroutines, there are two major thread pools: Dispatchers.Default
and Dispatchers.IO
. Both are bounded, with Dispatchers.Default
containing as many threads as there are cores in your machine and Dispatchers.IO
containing 64 threads. If all these threads are blocked on some operation, then the IDE encounters thread starvation – a situation where coroutine machinery cannot progress because all threads in a thread pool are blocked.
The usual symptom of thread starvation is coroutines being stuck in the Cancelling
state. They cannot move into Cancelled
because they cannot execute cleanup actions on their respective thread pools.
To investigate thread starvation of the Default
dispatcher, you need to find all blocked threads that contain runDefaultDispatcherTask
.
For example:
"DefaultDispatcher-worker-9@28483" daemon prio=5 tid=0xa0 nid=NA waiting java.lang.Thread.State: WAITING at jdk.internal.misc.Unsafe.park(Unsafe.java:-1) at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269) ... at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:48) at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source:1) (!!) at com.intellij.platform.pluginManager.frontend.BackendUiPluginManagerController.awaitForResult(BackendUiPluginManagerController.kt:293) ... at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:610) (!) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runDefaultDispatcherTask(CoroutineScheduler.kt:882) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:906) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:775) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:762)
If there are as many such threads as cores in your machine (at (!)
), then you have signs of thread starvation. Similarly, if you’re dealing with thread starvation of the IO
dispatcher, you’ll have 64 blocked threads named DefaultDispatcher-worker-N
.
NB: It is not enough to look at all threads that are named DefaultDispatcher-worker-N
to conclude that there is thread starvation of the Default
dispatcher. The coroutine scheduler shares physical threads between the Default
and IO
dispatchers, so it is difficult to say which thread belongs to which dispatcher without actually looking at the source code.
To fix thread starvation, blocking operations need to be moved out of the Default
dispatcher. Here we see that runBlocking
is used in the line with (!!)
, and it needs to be moved to a different dispatcher.
Another frequent source of locks in the IntelliJ Platform is service initialization. Since services are initialized once, a thread that tries to access a service can block while waiting for initialization on another thread.
Here’s an example of this scenario:
"AWT-EventQueue-0" #59 [128003] prio=6 os_prio=31 cpu=3011.23ms elapsed=172.40s tid=0x000000012c07ae00 nid=128003 waiting on condition [0x000000039b0ae000] java.lang.Thread.State: TIMED_WAITING (parking) at jdk.internal.misc.Unsafe.park(java.base@21.0.7/Native Method) ... (!) at com.intellij.serviceContainer.ComponentManagerImplKt.runBlockingInitialization$lambda$9(ComponentManagerImpl.kt:1660) ... at com.intellij.serviceContainer.ComponentManagerImpl.getService(ComponentManagerImpl.kt:672) (!!) at com.intellij.xdebugger.XDebuggerManager.getInstance(XDebuggerManager.java:32) ... (!!!) at com.intellij.platform.locking.impl.NestedLocksThreadingSupport.runWriteAction(NestedLocksThreadingSupport.kt:939) ... at java.awt.EventDispatchThread.run(java.desktop/EventDispatchThread.java:92)
We see that the EDT is blocked on the initialization of a service in the line marked (!)
. The service is XDebuggerManager
(at (!!)
), so there might be something wrong inside it. Additionally, we note that this process is running under the write action (at (!!!)
), an observation that will be useful later.
By searching for XDebuggerManager
, we can find the following thread:
"DefaultDispatcher-worker-4" #55 [130051] daemon prio=5 os_prio=31 cpu=1537.10ms elapsed=172.41s tid=0x000000012b883c00 nid=130051 in Object.wait() [0x000000036b607000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait0(java.base@21.0.7/Native Method) ... (!) at com.intellij.openapi.application.impl.ApplicationImpl.runReadAction(ApplicationImpl.java:1028) at com.intellij.xdebugger.impl.breakpoints.XBreakpointManagerImpl.loadState(XBreakpointManagerImpl.java:536) at com.intellij.xdebugger.impl.XDebuggerManagerImpl.loadState(XDebuggerManagerImpl.java:388) (!!) at com.intellij.xdebugger.impl.XDebuggerManagerImpl.loadState(XDebuggerManagerImpl.java:81) ... at com.intellij.configurationStore.ComponentStoreWithExtraComponents.initComponentBlocking(ComponentStoreWithExtraComponents.kt:43) ... at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:765)
The service instance initialization at (!!)
tries to acquire the read lock (at (!)
). The problem is clear – the write action on the EDT is blocked on the lock in service init, and service init is blocked on the read lock acquisition. This is a deadlock because of the incorrect order of the two locks.
The best way to fix it is to move the read action out of service initialization. If this is not possible, you can preload the service in the read action. This way, service initialization will inherit read access.
runBlocking
Sometimes, you may notice that a thread is blocked in runBlocking
. This means that it is trying to execute coroutines synchronously.
For example:
"JobScheduler FJ pool 6/15" prio=0 tid=0x0 nid=0x0 waiting on condition java.lang.Thread.State: TIMED_WAITING on kotlinx.coroutines.BlockingCoroutine@41813fd4 at java.base@21.0.8/jdk.internal.misc.Unsafe.park(Native Method) ... at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$BuildersKt__BuildersKt(Builders.kt:85) ... (!) at com.intellij.openapi.progress.CoroutinesKt.runBlockingCancellable(coroutines.kt:117) at com.intellij.grazie.text.CheckerRunner.run(CheckerRunner.kt:53) ... (!!) at com.intellij.openapi.application.impl.ApplicationImpl.tryRunReadAction(ApplicationImpl.java:1206) ... at java.base@21.0.8/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)
First, we need to make sure that runBlockingCancellable
is present (at (!)
). This is a special platform version of runBlocking
that works well with our cancellation machinery.
Since this thread runs the read action (at (!!)
), we now need to figure out why it cannot be canceled. To do this, we need to look in the coroutine dump and search for the coroutine tree starting with BlockingCoroutine
(this is the name of the coroutine corresponding to runBlocking
). Here’s what we find:
- JobImpl{Active} - BlockingCoroutine{Active}@41813fd4, state: SUSPENDED [ModalityState.NON_MODAL, ComputationState(level=0,thisLevelLock=com.intellij.core.rwmutex.RWMutexIdeaImpl@1c825ccf,isParallelizedRead=true), BlockingEventLoop] at com.intellij.grazie.text.CheckerRunner$run$1.invokeSuspend(CheckerRunner.kt:69) - DeferredCoroutine{Active}, state: SUSPENDED [ModalityState.NON_MODAL, ComputationState(level=0,thisLevelLock=com.intellij.core.rwmutex.RWMutexIdeaImpl@1c825ccf,isParallelizedRead=true), BlockingEventLoop] at com.intellij.ml.grazie.pro.SentenceBatcher$forSentences$1.parseAsync(SentenceBatcher.kt:96) at com.intellij.ml.grazie.pro.CloudOrLocalBatchParser.parseAsync(CloudOrLocalBatchParser.kt:20) at com.intellij.ml.grazie.pro.ParsedSentence$Companion.getSentences(ParsedSentence.kt:104) at com.intellij.ml.grazie.pro.AsyncTreeRuleChecker.checkExternally$suspendImpl(AsyncTreeRuleChecker.kt:18) at com.intellij.ml.grazie.pro.AsyncTreeRuleChecker$Style.checkExternally(AsyncTreeRuleChecker.kt:48) at com.intellij.grazie.text.CheckerRunner$run$1$deferred$1$1.invokeSuspend(CheckerRunner.kt:56)
We see that this coroutine runs at com.intellij.grazie.text.CheckerRunner
, which means that this BlockingCoroutine
corresponds to runBlocking
in the above trace. Now we can inspect the coroutine tree and try to find out the cause of the freeze.
Since the state of the coroutine is DeferredCoroutine{Active}
, it is not canceled. This means something is wrong with cancellation in the platform, as the platform should cancel all read actions on an incoming write action; hence, all coroutines should also be canceled. Normally, we should see BlockingCoroutine{Cancelling}
here.
To fix this UI freeze, we need to figure out why the coroutine cannot transition to the {Cancelled}
state. Most likely, the code does not call ProgressManager.checkCanceled()
frequently enough.
In this blog post, we explored common sources of UI freezes in IDEs based on the IntelliJ Platform. Most often, the freezes are caused by insufficient cooperation between plugin code and the IntelliJ Platform. By using the IntelliJ Platform’s primitives for executing cancellable operations, we can greatly improve the responsiveness of JetBrains IDEs.
]]>The most important update affects how LSP functionality is distributed in JetBrains IDEs:
Immediate impact (2025.2): The LSP API, which is already available to IntelliJ IDEA Ultimate users, will now be accessible for free within a limited feature set when their subscription expires. This change addresses long-standing feedback from plugin developers and smaller language communities, ensuring users no longer need to consider licensing restrictions when choosing between platforms.
Future changes (2025.3): With the new unified distribution, the traditional Community Edition will be sunset. All users will download a single IntelliJ IDEA installer, with IntelliJ IDEA Ultimate features requiring a subscription to be unlocked, but LSP support will remain available to everyone.
The LSP implementation itself stays closed-source and commercial, but we’ll keep it fully accessible for third-party plugins at no charge. This means plugin developers can now target a much broader user base without requiring their users to purchase the IntelliJ IDEA license.
This change addresses one of the most persistent complaints from the developer community. Previously, plugins using the LSP were essentially limited to IntelliJ IDEA Ultimate users, creating a significant barrier for:
The unified approach means plugin developers can now:
The LSP API in the IntelliJ Platform has matured significantly since its introduction in 2023.2 and currently supports the following:
Core language features:
Advanced features:
Plugin developers should target IntelliJ IDEA Ultimate 2025.2.1+ and include the optional dependency on the com.intellij.modules.lsp
module in the plugin.xml
file.
<idea-plugin> <depends optional=”true” config-file="lsp.xml">com.intellij.modules.lsp</depends> </idea-plugin>
To ensure the plugin is only available for installation in 2025.2.1+ and later, specify the since-build property in your build.gradle.kts
file with:
intellijPlatform { pluginConfiguration { ideaVersion { sinceBuild = "252.25557" } } }
If your project is based on the Plugin Template, set the relevant build number in the gradle.properties
file:
pluginSinceBuild=252.25557
The following KotlinLspServerDescriptor
creates an LSP support provider for the Language Server for Kotlin that handles Kotlin files (.kt
extension). The provider will ensure that the language server runs in a separate process from the kotlin-lsp
binary available in the PATH
environment variable.
private class KotlinLspServerDescriptor(project: Project) : ProjectWideLspServerDescriptor(project, "KN Kotlin LSP") { override fun isSupportedFile(file: VirtualFile) = file.extension == "kt" override fun createCommandLine() = GeneralCommandLine("kotlin-lsp", "--stdio") }
Create an LSP support provider that initiates the LSP server when needed, using the appropriate server descriptor.
class KotlinLspServerSupportProvider : LspServerSupportProvider { override fun fileOpened( project: Project, file: VirtualFile, serverStarter: LspServerSupportProvider.LspServerStarter, ) { if (file.extension == "kt") { serverStarter.ensureServerStarted( KotlinLspServerDescriptor(project) ) } } }
The LSP support provider must be registered in the plugin descriptor, next to the main plugin.xml
, within the existing lsp.xml
file.
<idea-plugin> <extensions defaultExtensionNs="com.intellij"> <platform.lsp.serverSupportProvider implementation="com.example.KotlinLspServerSupportProvider"/> </extensions> </idea-plugin>
You can find the above LSP implementation as a Kotlin Notebook example here: https://github.com/Kotlin/kotlin-notebook-intellij-platform/blob/master/examples/lsp.ipynb
The IntelliJ Platform documentation is clear about positioning: The LSP approach shouldn’t be considered as a replacement for the existing language API but rather as an added value. While LSP development offers significant advantages, such as reduced maintenance effort, faster development cycles, and cross-platform consistency, it is important to keep the following limitations in mind:
The LSP serves as a practical, lightweight solution for plugin developers seeking to reduce complexity and expand their reach while delivering consistent language support. For those who need deeper integration with IntelliJ Platform features, native APIs like the PSI remain the preferred choice.
To explore LSP integration, plugin developers should:
Removing commercial licensing for LSP integration on the IntelliJ Platform eliminates a significant problem for plugin developers, allowing them to prioritize efficiency and feature work.
More importantly, this shift represents more than a purely technical change; it underscores JetBrains’ recognition that language server development should be accessible to the broader developer community, not just those building commercial products.
While LSP integration won’t replace every native API, it provides a practical, lightweight solution for delivering consistent language support across all JetBrains IDEs. This fosters competition and presents an opportunity for plugin creators to target a broader audience. With the 2025.2 release, plugin authors can now evaluate and adopt the LSP on its technical merits, free from licensing constraints.
]]>
The ability to extend and customize software, from game mods to browser extensions, gives users a sense of freedom and the chance to bring their ideas to life. JetBrains IDEs already provide this sense of freedom through their support for plugins.
For some time now, creating plugins for JetBrains IDEs has been possible, but getting started with these sorts of projects can be intimidating. Navigating documentation, project templates, IntelliJ Platform dependencies, and Gradle configurations can be overwhelming for those wanting to experiment and test their ideas in real-world scenarios. Luckily, there’s now a more efficient option!
Kotlin Notebook is an interactive platform for data analysis, visualization, and prototyping with Kotlin. Like Jupyter Notebook, it provides a robust environment for data scientists, engineers, and researchers to explore and showcase their work within a dynamic, executable document. It combines Kotlin’s capabilities with the interactiveness of notebooks, allowing you to write and run code while seeing the results and visualizations in the same environment. This encourages an iterative workflow, boosting productivity and collaboration on data projects.
Building on the Kotlin Notebook plugin, we have started exploring how to connect it with the IntelliJ Platform. This enables the direct execution of IntelliJ Platform code from a notebook file within the active IDE runtime. The iterative workflow of Kotlin notebooks allows you to brainstorm, build, and test plugin features faster. The integration with the IntelliJ Platform makes moving the finished code into your plugin easy.
This initial idea has developed into a strong integration, and as of the 2025.2 release, the functionality is now available for use.
To get started, create a new Kotlin notebook either as a new file in your project or as a scratch file, using ⌘⇧N on macOS or Ctrl+Shift+N on Windows/Linux.
Notebooks are based on the concept of cells. To start using this integration, switch to Run in IDE Process mode, create a new code cell, and declare:
%use intellij-platform
Due to the technical issues on Windows, it’s recommended to explicitly request the latest available version until the IntelliJ IDEA 2025.2.1 is released using:
%useLatestDescriptors %use intellij-platform
Once you do this, the necessary IntelliJ Platform libraries will be loaded into the notebook’s classpaths, making them available for import in the subsequent cells.
The ability to run code within the same IDE environment removes the need to build a full plugin project to test UI components. The runInEdt {}
The helper ensures that the code runs on the Event Dispatch Thread (EDT), catching any exceptions and showing them below your cell.
Returning UI elements as a response when working with the Kotlin UI DSL or standard Swing components renders them immediately. All rendered components stay fully interactive.
The integration uses the Disposer mechanism available in the IntelliJ Platform. A dedicated global notebookDisposable
variable allows for registering and later disposing of specific notebook elements, such as extensions, listeners, actions, or UI components. This ensures the IDE manages the lifetime of your objects when you close the project or restart the notebook.
// Register something that needs cleanup Disposer.register(notebookDisposable, myDisposable) Disposer.register(notebookDisposable) { // Cleanup components directly }
When you integrate your notebook with the IntelliJ Platform, only the core platform is initially loaded into the classpath, similarly to when you’re working on a standalone plugin. However, you may need to depend on and use other plugins, whether bundled or installed from JetBrains Marketplace. To load plugins installed in your IDE, you must explicitly call the loadPlugins(vararg pluginIds: String)
helper.
// Load specific plugins loadPlugins("Git4Idea", "com.intellij.java")
In traditional plugin development, extensions are defined in the plugin.xml
file. With Kotlin notebooks, you can dynamically register extensions using registerExtension()
, avoiding the need for a structured plugin project.
For example, to register a custom ChatMessageHandler
and ChatAgent
for the AI Assistant plugin, you can simply run the following:
loadPlugins("com.intellij.ml.llm")
class MyChatMessageHandler : ChatMessageHandler { override fun isApplicable(project: Project, kind: ChatKind, userMessage: UserMessage) = true override fun createAnswerMessage( project: Project, chat: ChatSession, userMessage: UserMessage, kind: ChatKind, ) = SimpleCompletableMessage(chat) override suspend fun serveAnswerMessage( project: Project, chat: ChatSession, answerMessage: ChatMessage, smartChatEndpoints: List<SmartChatEndpoint>, ) { ChatAgent.EP_NAME.extensionList .find { it.id == "groot" } ?.serveAnswerMessage(project, chat, answerMessage) } } registerExtension(ChatMessageHandler.EP, MyChatMessageHandler())
class MyChatAgent : ChatAgent { override val id = "groot" override val name = "I am Groot" override fun createAnswerMessage( project: Project, chat: ChatSession, userMessage: UserMessage, kind: ChatKind, ) = SimpleCompletableMessage(chat) override suspend fun serveAnswerMessage( project: Project, chat: ChatSession, answerMessage: ChatMessage, ) { answerMessage as SimpleCompletableMessage answerMessage.appendText("*I am Groot*".privacyConst) } } registerExtension(ChatAgent.EP_NAME, MyChatAgent())
Similarly, you can register a custom UI theme as follows:
import com.intellij.ide.ui.UIThemeProvider val theme = UIThemeProvider().apply { id = "My Theme" path = "Theme.json" } registerExtension(UIThemeProvider.EP_NAME, theme)
This approach allows you to experiment and test extensions instantly within the IDE runtime, streamlining development and saving time.
The integration provides several ways to access the internal details of the currently running IDE, such as its build details and location in the file system. Exposed mechanisms such as the Plugin Manager and Plugin Repository allow for interacting with plugins available in JetBrains Marketplace, or even ones installed locally. All available variables and helper methods are documented in the relevant section in the IntelliJ Platform SDK documentation.
// Access IDE information productInfo.name // "IntelliJ IDEA" productInfo.version // "2025.3" productInfo.buildNumber // "253.xxxx" idePath // “/Users/hsz/Applications/IntelliJ IDEA Ultimate 2025.3 Nightly.app/Contents” ide // com.jetbrains.plugin.structure.ide.Ide instance // Work with the plugin manager pluginManager.findEnabledPlugin(PluginId.getId("com.intellij.java")) // Request JetBrains Marketplace pluginRepository.pluginManager.searchCompatibleUpdates( build = "${productInfo.productCode}-${productInfo.buildNumber}", xmlIds = listOf(“org.jetbrains.junie”), channel = “default”, )
Integrating Kotlin Notebook with the IntelliJ Platform represents a significant step forward for custom plugin development. With traditional barriers associated with project setup, compilation, and deployment removed, developers can now concentrate more effectively on implementing their ideas.
We already have great ideas for ways to improve this experience further:
Follow these simple steps to get started:
%use intellij-platform
.If you’d like to share feedback, please visit the JetBrains Platform forum, where we can discuss any challenges and ideas for improving this integration. You can also join the #notebooks public Slack channel for discussions and questions.
The IntelliJ Platform integration and the examples featured in this article are now publicly accessible on GitHub, along with a dedicated section in the IntelliJ Platform SDK documentation. We welcome pull requests with ideas to improve the user experience.
Happy hacking!
Jakub
With the latest TeamCity release, we’re addressing that problem head-on. Say hello to TeamCity recipes: customizable build steps that can be reused, versioned, and published on the JetBrains Marketplace (or used internally in your organization).
Whether you’re managing dozens of pipelines or just want to save time setting up repeatable build logic, recipes are designed to streamline your CI/CD workflows. Let’s take a closer look at what recipes are about.
Recipes are build steps you can save and reuse whenever you need to repeat the same setup. They help you avoid reinventing the wheel by letting you define and reuse common build logic.
Recipes can be:
Each recipe can include configurable parameters, documentation, and versioning, making them easy to integrate and maintain.
For more information about what recipes are and how they’re different from meta runners, please refer to our documentation.
For the first time, external users can publish their own recipes to the JetBrains Marketplace. This opens the door to a community-driven ecosystem of reusable build logic, maintained and shared by TeamCity users around the world.
To upload your own recipes to the Marketplace, follow these steps.
Our new interface provides a more user-friendly installation experience:
This transparency ensures that users can review and trust third-party contributions.
Previously known as meta runners, private recipes can now be written in YAML. This modern syntax improves readability and maintainability. While XML is still supported for backward compatibility, YAML is now the recommended format.
Manage both public and private recipes from a single interface:
Whether you’re reusing steps within your organization or using recipes created within your organization or outside of it, this feature offers a practical way to reduce duplication and simplify maintenance.
Public recipes give you access to a growing library of build steps created by the TeamCity community. Because each recipe is a small, self-contained file with visible source code, you can review what it does before using it.
Private recipes, on the other hand, help teams standardize their internal workflows. Written in YAML, they’re easier to read, edit, and debug compared to older XML-based meta runners. And because they’re scoped to your TeamCity project or organization, they stay private and secure.
Both public and private recipes are easier to create and share than full-fledged plugins, lowering the barrier to contribution.
The idea behind recipes might feel familiar: many CI tools offer reusable build logic. But with TeamCity Recipes, we’ve taken a more opinionated approach to how they’re configured, shared, and executed, focusing strongly on security, transparency, and ease of use.
Unlike some other ecosystems, TeamCity doesn’t default to using floating versions like latest, which can introduce unintentional changes into your builds. When you install a recipe, you choose a specific version, giving you more control and predictability.
When a new version becomes available, TeamCity will notify you, but won’t auto-upgrade anything without your knowledge and permission.
Every public recipe comes with visible source code you can inspect directly from the TeamCity UI before installing and from the overview page after the installation. Once installed, the build step editor automatically adapts to the Recipe’s parameters, presenting clear controls and descriptions instead of relying on manual text input.
This makes Recipes not only easier to trust, but also easier to configure and debug, especially for teams that value visibility and safety.
Publishing a recipe is simple:
Please also refer to our documentation for more information on how to upload a new recipe.
Recipes are now live in TeamCity. Browse the Marketplace, experiment with public recipes, or create your own to simplify and supercharge your CI/CD pipelines.
We believe this feature will help foster a more vibrant, collaborative TeamCity community, and we can’t wait to see what you build 🚀
Stay tuned for upcoming tutorials and videos showcasing what’s possible with recipes!
]]>All the changes made to the editor in the 2025.2 release are based on a single underlying idea: to make remote editing feel as seamless and responsive as working locally.
We improved the way the editor opens in remote sessions. To reduce the perceived delay between user action and UI feedback, we’ve changed how fast different UI elements appear when opening a file. Now, when a file is opened in a remote session, the editor tab appears immediately, first with the tab frame and file name, followed by the file content as soon as it’s available.
We’re also experimenting with a skeleton view for cases where the editor content cannot be displayed fast enough, with the goal of making the UI feel more responsive. Once the data arrives, the skeleton is seamlessly replaced by the actual file content. Please share your feedback on this change!
To improve responsiveness, we’ve moved several basic editor actions to the frontend:
Using the Java plugin as an example, we’ve made progress toward smarter frontend execution by moving more functionality to the frontend. This includes:
Thanks to these changes, the caret now moves much more predictably, even after smart backend updates are applied.
We’ve also extended this approach to SQL, JSON, YAML, TOML, and Ruby, which are now all available in the released version.
There is more work ongoing for upcoming releases, including native-feeling remote development support for other programming languages.
We’ve started rolling out a split frontend/backend architecture for the debugger. One of the biggest advantages is that the debugger is less coupled with the network delay. For instance, if you place or delete a breakpoint, it will be applied immediately, with a subsequent interaction with the backend. We’ve also added support for core debugging features like frames, variables, watches, and more, and we’re continuing to work on developing additional features.
While not all functionality is in place yet, the current implementation is fully usable, and the missing features don’t block core debugging workflows.
The initial implementation of the split terminal was written between 2024.3 and 2025.1. We finally enabled it by default in 2025.2. The new release fixed many issues related to the previous version of the terminal, and the change was highly anticipated by many individual and corporate customers.
These updates improve the current experience and lay the foundation for future enhancements, ensuring new features will now work natively in remote development mode.
Popups with long or dynamic lists have historically performed poorly in remote development scenarios, especially over unstable or high-latency connections. The redesigned versions now provide the same native-level performance when working remotely as they do when working locally, offering smooth scrolling and instant selection, even on slower or less reliable networks.
With the latest release, we introduced synchronization of plugins between the client and the host. The IDE now installs the necessary plugins on the client machine based on the host’s setup and vice versa. This allows the development environment to remain consistent with minimal user involvement. The synchronization behavior can be configured depending on the security requirements in specific companies.
We fixed an issue where various project settings were lost between IDE restarts. Recent updates make sure that selected UI and project-specific settings are preserved so that you can resume work exactly where you left off.
Here’s what now persists correctly:
Remote development support in Toolbox was released in April, and while there’s still room for improvement, early feedback has been very positive. Several companies have confirmed that using Toolbox significantly improves connection stability.
In synthetic tests, we observed connection performance improvements of 1.5x or more:
In addition to performance gains, Toolbox supports OpenSSH, works with any major remote host’s OS (not just Linux, but Windows and macOS, too), and lets you manage everything from setup to updates in the same way you handle your IDEs locally. This results in a smoother remote workflow that’s built for how you actually work. You can read more about remote development with Toolbox in our recent blog post.
We’ve also added a new feature: If Toolbox is running, you can now see remote projects in the Recent Projects popup, right alongside your local projects.
This year, we focused on improving core functionality – frequently used windows, actions, and better separation of components and languages between the frontend and backend. Our goal is to build a unified architecture that works consistently in both monolith and remote development environments.
That said, there are still some tricky parts of the IDE stack to tackle, like syncing keymaps, color schemes, and other settings.
We’ve also fixed several bugs. Here are some of the most important ones:
Functionally, it is still the same terminal, but it comes with some novel visual additions.
We’ve added separators between executed commands to make it easier to distinguish where commands start and end.
If you wish to hide these separators, you can do so by going to Settings | Tools | Terminal and disabling the Show separators between executed commands option.
For now, this feature is only available when running Bash or Zsh, but we plan to implement it for PowerShell in the next release. You can follow this YouTrack issue for updates.
Updated color scheme
We have updated the color schemes in both light and dark themes to improve the readability of command outputs and alternate buffer app renderings.
If necessary, you can customize these color schemes in Settings | Editor | Color scheme | Console colors | Reworked terminal. If you experience any specific color issues, we encourage you to report these on IJPL-187347.
Improving performance was one of our main goals, and the reworked terminal is now several times more performant on fast command outputs than the classic terminal.
The reworked terminal was built considering the architectural requirements necessary to provide the same user experience and quality in both local and remote development.
We tried our best to achieve feature parity with the classic implementation, but if some scenarios are not covered in the reworked terminal, you can switch to the classic terminal via the Terminal engine option in Settings | Tools | Terminal.
There are many internal and external dependencies on the Classic Terminal API, so it will remain available for at least two more releases to ensure a smooth transition both for users and plugin writers.
Some functionalities in third-party plugins that rely on the classic terminal implementation might be limited in 2025.2. This might affect plugins that get text from, execute commands in, and add custom key bindings to the classic terminal. We already informed plugin vendors about API changes and published some technical details in the platform community forum.
We’re still working on further terminal improvements and would be grateful for your input.
If you have any feedback, feel free to submit it either from the IDE by clicking Share your feedback in the three-dot menu in the Terminal tool window or by submitting a new ticket to our bug tracker.
]]>Plugin Developer Conf returns on November 5, 2025, and we’re looking for speakers to join the lineup! This free, one-day online event brings together the JetBrains plugin developer community to share insights on building and managing plugins.
Last year’s online conference welcomed over 2,500 online attendees and featured sessions from eleven expert speakers. If you have lessons, tips, or stories about plugin development, maintenance, or overcoming challenges, submit your talk proposal by July 15, 2025.
Learn more and apply in this blog post.
To comply with the Omnibus Directive and Digital Services Act, JetBrains Marketplace requires vendors who identify as traders to provide additional verification details. This includes contact information, identification documents, and banking details. Vendors who haven’t completed this process may face suspension.
Read the full blog post to ensure you’re meeting the latest requirements.
With the release of PyCharm 2025.1, PyCharm is now a single, unified product offering core features for free and a Pro subscription for advanced tools. In response to this change, JetBrains Marketplace has updated the way PyCharm category is displayed: the “PyCharm Community” has been removed from product filters and dropdowns, and “PyCharm Professional” has been renamed to “PyCharm.” Plugin compatibility labels have also been revised to clearly distinguish between plugins compatible with the full PyCharm IDE and those specific to the Community edition.
The IntelliJ Plugin Verifier checks binary compatibility between IntelliJ-based IDE builds and plugins. Version 1.388 introduces several structural improvements and new features, including better handling of content module dependencies, support for Fleet products that follow IntelliJ versioning, and enhanced TeamCity recipe functionality. It also includes updates to ByteBuddy and Gradle, and fixes an issue with missing plugin-declared dependencies.
Check out the changelog for full details.
The IntelliJ Platform Gradle Plugin is a plugin for the Gradle build system to help configure your environment for building, testing, verifying, and publishing plugins for IntelliJ-based IDEs.
Version 2.6.0 brings new helper methods to simplify resolving plugin dependencies from JetBrains Marketplace, adds support for CSS and XML test frameworks, and improves task output and warnings for Plugin Verifier runs. It also deprecates Aqua (QA) as a target platform and includes a range of internal fixes and dependency resolution improvements.
You can find the full changelog here.
In a recent session, IntelliJ Platform Developer Advocate Róbert Novotný walked through the Plugin Verifier, one of the most important tools for maintaining plugin compatibility across JetBrains IDEs. The session focused on spotting breaking API changes, avoiding internal APIs, and verifying plugin behavior across IDE versions.
Watch the recording or check out the key takeaways in our blog post.
Following the enthusiastic response to our Plugin Testing: Performance, UI, and Functional Testing session at JetBrains Plugin Developer Conf 2024, we launched a blog series that takes a deeper dive into plugin testing strategies. The fourth installment shows how to automate your integration tests using GitHub Actions, helping you catch issues early and run tests across different environments.
This new article provides a comprehensive overview of the New Project Wizard API in the IntelliJ Platform, which powers project and module creation dialogs. It explains the architecture behind language and framework project generators, how to build and chain wizard steps, manage shared data, and create responsive UIs with property graphs. The guide also covers advanced features like branching logic, persistent defaults, and support for custom build systems — offering practical insights for anyone implementing project wizards.
]]>Visual context is always changing – and so should the look and feel of the tools you use every day. We know how much a familiar UI matters, and we’ve heard your feedback from past updates. That’s why we’re taking an iterative, careful approach this time: small, focused improvements, shared early, with your feedback guiding what comes next.
In the latest 2025.2 EAP of JetBrains IDEs, you can find two new UI styles: One Island and Many Islands, each available with both dark and light modes. Ultimately, one of these styles will become the new default – your feedback will help us decide which one.
This update is purely visual – all functionality remains unchanged. You can switch between the new styles and your previous theme at any time.
With this update, we aim to achieve several goals:
One Island style
Many Islands style
We invite you to try the new styles and share your thoughts. After using a new theme for a while, you’ll get a prompt to fill out a short survey that shouldn’t take more than five minutes to complete. Your feedback will help us decide which style to finalize and roll out as the default.
We’re already working on some known issues. Please vote and comment on them, or submit a new issue if you notice something we haven’t already been made aware of.
]]>
Plugin Developer Conf is a free, live, online event hosted by JetBrains, created for and shaped by the plugin development community. It’s a one-day virtual gathering focused on all aspects of building and managing plugins for JetBrains products.
The first-ever Plugin Developer Conf took place last year, bringing together over 2,500 online attendees and featuring sessions from eleven experienced speakers who shared practical insights on plugin testing, localization, user feedback, and more.
This year, we’re gathering the community once again to exchange ideas, lessons, and strategies for building better plugins. On November 5, 2025, we’ll host the next edition of the conference, and we’re currently putting together the agenda.
We’re now looking for speakers! Whether your talk covers building and maintaining plugins, overcoming challenges, or sharing best practices — we’d love to hear from you. Your experience could help fellow plugin authors on their journey.
You’re welcome to submit any talk related to plugin development or JetBrains Marketplace, but here are a few topics we’re especially interested in:
To give you an idea, here are some talks from 2024:
All talks will be presented live in English and can run for 30 or 45 minutes, followed by an optional 5–10 minute Q&A. We’ll schedule your session during business hours in your time zone and are happy to support you with dry runs, demo feedback, and presentation prep if needed.
Talks will be streamed and recorded, then shared on JetBrains YouTube, newsletters, blogs, and other official channels. You’re welcome to use a slideshow, live demo, or any format that best suits your content.
As a thank-you, selected speakers will receive a 1-year personal JetBrains All Products Pack and, if you’d like, we’ll also help promote your blog, project, or course alongside your talk.
The Call for Speakers will remain open until July 15, 2025.
Not sure if your talk idea will fit? Reach out to elena.kerpeleva@jetbrains.com and we can discuss!
Excited to hear your stories — submit your talk today!
]]>If you couldn’t join us live, the full recording is now available on JetBrains TV.
And if you prefer reading over watching, this blog post sums up the key takeaways from the session.
The IntelliJ Plugin Verifier is a tool that ensures a plugin behaves correctly across JetBrains IDE versions. It checks for compatibility issues with the evolving IntelliJ Platform APIs, and validates that a plugin:
JetBrains Marketplace relies on the Plugin Verifier to assess plugins upon upload, acting as a quality gate for all listed plugins.
Publishing a plugin to JetBrains Marketplace is about more than just passing basic validation. End users expect plugins to work reliably, without errors or unexpected behavior. The Plugin Verifier helps plugin authors ensure their plugins will run smoothly across IDE versions, minimizing user disruption.
The session covered several types of issues that the Plugin Verifier can detect:
Example: Plugin verification results
To avoid the delay of uploading a plugin just to see it fail verification, developers can and should run the Plugin Verifier locally. There are two main ways to do this:
verifyPlugin
and verifyPluginStructure
for integrated verification.The session also walked through how to set up both methods, showing how developers can validate plugins against multiple IDE versions, identify API compatibility problems, and fix issues before publication.
Integrating the Plugin Verifier into a continuous integration pipeline saves time and prevents issues from reaching production. The verifier can be run against new plugin versions and upcoming IDE releases to catch problems proactively.
While the Plugin Verifier highlights potential problems, not all warnings are show-stoppers. Some rules (like those about naming or older plugin IDs) can be muted locally using CLI flags (e.g., --mute
). However, JetBrains Marketplace may enforce stricter rules, and muted issues locally do not affect Marketplace verification.
To get the most out of the Plugin Verifier and to avoid common pitfalls later on, keep these practical tips in mind:
The Plugin Verifier is a crucial tool in the JetBrains plugin development workflow. Whether integrated via Gradle or run standalone, it empowers developers to ship robust, future-proof plugins. By embracing it early in the development process, plugin authors can ensure a smoother path to publication and better experiences for their users.
]]>