← all articles

Why Appium's Java client and Selenium versions have to match

If you write Android tests in Java, you will eventually add two dependencies to your build file: io.appium:java-client and org.seleniumhq.selenium:selenium-java. They look like independent libraries. They are not. The Appium Java client is a layer built on top of one specific generation of Selenium, and if the two disagree you get errors that look unrelated to versions at all: NoSuchMethodError, NoClassDefFoundError, or a session that refuses to start.

I hit this the first time I upgraded an old test project, and it cost me an afternoon because the stack trace never mentioned the word “version”. If you landed here from Stack Overflow question 51416139, the short answer is below and the long answer is the rest of the article. Short answer: pick the java-client version first, read which Selenium version it was compiled against in its compatibility table, and pin Selenium to exactly that version in your build file. Do not let Maven or Gradle choose it for you.

What it is

The Appium Java client (the java-client project on GitHub, maintained by the Appium organisation) is a library that adds mobile features to Selenium’s Java bindings. Selenium gives you classes like RemoteWebDriver, By, WebElement and Capabilities. The Appium client extends those with AndroidDriver, IOSDriver, AppiumBy, and mobile commands such as installing an app or pressing a hardware key.

“Extends” is the important word. AndroidDriver is literally a subclass of Selenium’s RemoteWebDriver chain. It calls Selenium’s internal methods and relies on Selenium’s internal classes for building HTTP requests, encoding commands and parsing responses. Those internals change between Selenium releases.

So “the versions have to match” means this: each java-client release is compiled against one Selenium version (or a narrow range). At runtime, the Selenium jar on your classpath must be binary compatible with what the java-client expects. If it is not, the JVM fails when the java-client calls a method that no longer exists, or exists with a different signature.

How it works

There are two layers to understand: the protocol, and the Java binary compatibility.

The protocol break between Selenium 3 and 4

Selenium 3 (the last release was 3.141.59, in 2018) spoke the JSON Wire Protocol by default. The W3C WebDriver specification, published by the W3C, was a competing standard that Selenium 3 supported only as an opt-in. Appium at that time had its own flavour, MJSONWP (mobile JSON wire protocol), with extra endpoints for touch gestures and other phone things.

Selenium 4 (released October 2021) made the W3C protocol the default and dropped the legacy path. That affected everything that used the old style:

  • capabilities: the W3C spec requires non-standard capabilities to carry a vendor prefix, so Appium’s settings became appium:deviceName, appium:automationName and so on
  • gestures: the old TouchAction and MultiTouchAction classes belonged to the old protocol. The W3C replacement is the Actions API (I wrote about that in touchaction vs the W3C actions api in appium)
  • session creation: Selenium 3 sent a legacy payload plus an optional W3C payload. Selenium 4 sends W3C only

The Appium side followed. The java-client 8.0.0 release moved to Selenium 4 and dropped the legacy protocol. Appium server 2.0 is W3C only, and Appium 1.x servers accepted both. That is why a Selenium 3 era test can still talk to an old Appium 1.x server, but an unmodified one fails against Appium 2.

Java binary compatibility

The protocol is the visible half. The invisible half is plain Java linking. When java-client 7.x was built, it compiled against Selenium 3.141.59 classes. When the JVM loads AndroidDriver and it calls a Selenium method, the JVM looks up that exact method by name and signature at that moment. If you dropped in Selenium 4 jars, the method might have moved, been renamed or been removed. The compile step was fine back then, so nothing warns you. The failure happens at runtime, often at the first line that creates a driver.

Build tools make this easier to trip over. java-client declares Selenium as a dependency, so Maven and Gradle pull in a Selenium version transitively. If you also declare selenium-java yourself, the build tool has to choose one. Maven uses a “nearest definition wins” rule, so your direct declaration usually beats the one inside java-client. Gradle by default picks the highest version. Either way, the winner is not necessarily the version java-client was built for.

How to read the compatibility matrix

The java-client README on GitHub includes a compatibility section: a table that lists java-client versions against the Selenium client version they were built with, and a note on the minimum Appium server. I won’t copy numbers here, because the table changes with each release and a stale copy would be worse than useless. As of October 2026, the reliable way to use it is:

  1. find the java-client version you want in the left column
  2. read the Selenium version in the matching row
  3. note whether the row says it supports Appium 1.x, 2.x or both on the server side
  4. set Selenium to that exact version, not a range

Roughly, the 7.x line sat on Selenium 3.141.59 and 8.x onward sits on Selenium 4. Check the table for actual patch versions.

The Appium project’s own documentation also covers the move to W3C capabilities and the Appium 2 server.

Why it matters

  • the errors are misleading: a mismatch rarely says “wrong version”. You get NoSuchMethodError, NoClassDefFoundError, ClassNotFoundException for a class that used to exist, or a SessionNotCreatedException because the server rejected a capabilities payload. People then waste time reinstalling the Android SDK or restarting adb when the fault is in pom.xml.
  • upgrades are coupled: you cannot upgrade Selenium alone to pick up a browser driver fix, then expect your mobile tests to keep working. Upgrading either library means checking the other. Treat them as one unit.
  • it affects your server choice: a Selenium 3 style project talking to an Appium 2 server fails on capabilities. Moving the server forward forces the client forward, which forces Selenium forward, which forces code changes such as replacing TouchAction.
  • it breaks CI quietly: a build that worked for months can break when a transitive dependency floats. If a farm or CI box resolves a different Selenium than your laptop, tests pass locally and fail remotely. If you run tests across many phones, such as in the setup I describe in what is an android device farm, one unpinned version across fifty runners is a painful surprise.

Common misconceptions

“Selenium is just the web part, so mobile tests don’t care about its version.” Wrong. The Appium client subclasses Selenium’s driver and uses its HTTP and JSON machinery. There is no separate mobile-only stack underneath.

“Newer Selenium is always safer.” Not with Appium. A newer Selenium than your java-client was built for can remove or change an internal method that java-client calls. Newer helps only when the java-client row in the matrix says so.

“If it compiles, the versions are fine.” The failure shows up when the runtime classpath differs, or when a call path is only exercised at session start. A green compile tells you little here.

“I can fix it by adding exclusions and ignoring the matrix.” Excluding the transitive Selenium and supplying your own is exactly how people end up mismatched. The sensible use of an exclusion is the opposite: exclude whatever floats, then declare one Selenium version that matches the matrix. In Maven, a dependencyManagement block pinning Selenium does this cleanly. In Gradle, a constraints block or a strict version does the same.

A fifth one that I hear a lot: “the protocol change only matters for the server.” It also changes your test code. Capabilities need the appium: prefix, and gesture code written with TouchAction has to move to the W3C Actions API. On newer java-client 8.x releases there are typed options classes, such as UiAutomator2Options, which build the prefixed capabilities for you.

Where to go from here

If you are fixing an existing project, here is the order I would work in:

  1. run mvn dependency:tree or gradle dependencies and look at which selenium-* jars are actually resolved. Compare them against the java-client row in the matrix
  2. pin Selenium explicitly, and keep every selenium-* artifact on the same version
  3. confirm your Appium server version, then check that capabilities are W3C style with the appium: prefix
  4. replace any TouchAction code using touchaction vs the w3c actions api in appium

Related reading on this site:

One practical note on hardware. Version problems are easiest to diagnose when you have a real, stable phone to run against, so you know the fault is in your build file and not in your emulator. If you do not have one to hand, cloudf.one rents real Android phones in Singapore on dedicated hardware, controlled from the browser. It has no self-service adb: adb and Appium reach a rented phone only through a WireGuard tunnel that we set up on request by email to [email protected]. Use it, or any device, only for apps you own or are authorised to test.

Written by Xavier Fok

disclosure: this article may contain affiliate links. if you buy through them we may earn a commission at no extra cost to you. verdicts are independent of payouts. last reviewed by Xavier Fok on 2026-10-20.

free download
The adb and scrcpy command cheat sheet

The commands we reach for on a rack of real phones, on one printable page. Leave your email and we will also tell you when we publish a new guide, a few times a month at most.

from the team behind cloudf.one
Passes on the emulator, fails on a real phone?

cloudf.one rents real Android phones in Singapore that you control from the browser, each on a persistent Singapore mobile IP. Real hardware, a real OEM build and a real carrier network, without buying and racking the phones yourself.

test on a real phone →
read on
More from ADB Handbook

adb commands, scrcpy flags, automation frameworks, and why a test passes on one phone and fails on the next.

browse all articles →