← all posts
// tooling · jetbrains

Surviving IntelliJ 2026.3 as a plugin author: OkHttp, read actions and a CI lane for the EAP

The IntelliJ IDEA 2026.3 EAP is out (build 263.3889.65), and the list of incompatible changes is the less interesting half of it. The half that worries me never makes that list. Changes to @ApiStatus.Internal and @Deprecated(forRemoval=true) are not announced there by design, so the only early warning you get is running Plugin Verifier against the EAP yourself. Per the radar notes I work from, that buys you one to three months before 2026.3 stable. It has happened already: in 2026.2, PluginManager was marked @Internal with no deprecation period at all.

I covered the Marketplace side of this in JetBrains Marketplace now flags internal API use. This is the follow-up, now that the API changes list has named names.

The changes that break, in order of how rudely they break

The bundled OkHttp is gone. A plugin that relied on the platform shipping it will compile fine and then die on the user's machine with NoClassDefFoundError. That is the worst kind, because your own tests pass if they run against an older platform. The fixes are java.net.http, shipping your own OkHttp inside the plugin, or whatever platform HTTP helper you trust (one of the notes mentions PlatformHttpClient, which I have not verified, so check the docs before you rewrite anything).

Kotlin UI DSL 1.0 is deleted, the whole com.intellij.ui.layout package. This one fails at compile time, which is almost friendly. You move to UI DSL v2, and if you have settings pages written in the old panel { row { ... } } style, budget real time, because the two DSLs are not a search-and-replace apart. The debugger, externalSystem and remoteServers modules have also been split out, so they need an explicit bundledModule(...) line in build.gradle.kts. And Sdk now extends UserDataHolderEx, which only matters if you implement or wrap it.

Then the softer ones. ReadAction.compute(), run() and runReadAction() are deprecated, because a blocking, non-cancellable read action on a background thread freezes the UI whenever a write action is waiting. In Kotlin the replacement is the coroutine readAction { } or smartReadAction { }. In Java it is ReadAction.nonBlocking(...).submit() or .executeSynchronously(). Deprecated means it still runs, so nothing crashes, which is exactly why it rots in the codebase. And LspServerManager is now LspClientManager, a rename you will find with one grep.

The removals are loud and the deprecations are quiet, and the quiet ones are what 1-star reviews about frozen editors are made of.

One verifier lane that blocks, one that is allowed to be red

Here is the CI arrangement I would use. The first lane runs Plugin Verifier against the IDE builds you actually claim to support, on every pull request, and it blocks the merge. The second lane runs against the 2026.3 EAP on a schedule (nightly is fine, weekly is the minimum) and on any PR that touches dependencies. It is not allowed to block releases of your current line, but it is not allowed to be ignored either. Red means a ticket with a date on it.

Treat the two lanes differently on failure level. On the stable lane, compatibility problems fail the build. On the EAP lane I also fail on internal and deprecated API usages, because that is the category that never shows up in the changes list. Noisy at first, yes. Fix the noise once and it stays quiet.

name: verify
on:
  pull_request:
  schedule:
    - cron: '0 5 * * *'
jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: temurin, java-version: 21 }
      - uses: gradle/actions/setup-gradle@v4
      - name: Cheap grep gate
        run: "! grep -rnE 'okhttp3|com.intellij.ui.layout' src/"
      - run: ./gradlew verifyPlugin

The grep gate is crude and I like it for that. It fails in seconds, before Gradle downloads a multi-gigabyte IDE, and it tells a contributor exactly which import to remove. Keep the list of patterns next to the migration notes and add to it as JetBrains adds removals.

Pointing the Gradle plugin at the EAP

With the IntelliJ Platform Gradle Plugin 2.x, verification targets live in the pluginVerification block. This is the shape I would start from. I wrote it from the plugin docs and have not run it against 2026.3 myself, so check the select options against the version of the plugin you have.

intellijPlatform {
    pluginVerification {
        ides {
            recommended()
            select {
                channels = listOf(ProductRelease.Channel.EAP)
                sinceBuild = "263"
            }
        }
        failureLevel = listOf(
            VerifyPluginTask.FailureLevel.COMPATIBILITY_PROBLEMS,
            VerifyPluginTask.FailureLevel.INTERNAL_API_USAGES,
            VerifyPluginTask.FailureLevel.DEPRECATED_API_USAGES,
        )
    }
}

Keep until-build conservative in plugin.xml until the EAP lane has gone green. Claiming 2026.3 support you have not verified is how a Marketplace listing ends up with a bad week.

Order of work

Start with the hard failures, OkHttp and UI DSL 1.0 and the module extraction, because they decide whether you can build and run at all. Read actions come second: they compile, so the pressure is off, but they are the ones that change behaviour, so each migrated call site needs a look at cancellation, not just a syntax swap. The LSP rename goes last, it is mechanical.

Then ship code, changelog and Marketplace entry together, as one release, not three separate chores. One question I cannot answer from here: which of your plugins pull OkHttp in through a transitive library that expects the platform to provide it? A grep on your own sources will not find that, and the verifier might only find it if the class reference is in your jar. Check your compileOnly dependencies before you trust a green build.

#jetbrains#plugins#ci#api-stability