Quick answer:
versionNameis a free-form string shown to users ("2.1.0").versionCodeis a positive integer that Android compares to decide whether an install is an upgrade, and Google Play rejects any upload whose code is not higher than every code you have used before. Start at 1 / "1.0" and only ever go up.
Side by side
versionName | versionCode | |
|---|---|---|
| Data type | String | Integer |
| Seen by | Users, on the listing and in Settings | Nobody |
| Compared by Android | Never | Always |
| Example | 2.1.0, Autumn release | 58 |
| Must rise on each upload | No | Yes |
Shipping version name "1.0" with version code 63 is perfectly valid. The name is decoration; the code is the mechanism.
Play's rule
Each upload needs a versionCode higher than any you have ever uploaded — not merely different. Play remembers every code, including ones from releases you deleted or that only reached an internal test track, and refuses anything at or below the highest. The message reads "Version code N has already been used", and the only way out is up.
Numbering schemes
Plain counter — 1, 2, 3…
The code is a counter; the name carries meaning. Cannot overflow, cannot confuse. Right for most projects, with the one weakness that the number says nothing by itself.
Semantic — 1.4.2 → 10402
major×10000 + minor×100 + patch. The release is readable straight from a crash report. Minor and patch cap at 99, which is rarely a problem.
Date — 260911
yyMMdd. Sortable and self-describing, but one release per day.
Date + build — 26091101
yyMMddBB, up to 100 builds a day; common in CI. Note it crosses Play's ceiling in 2042.
The ceiling: 2,100,000,000
Play refuses any version code above 2,100,000,000 — just under the signed 32-bit limit. A scheme like yyyyMMddHH produces 2026091114 today, which is already over the line and rejected on first upload. Use a two-digit year, or do not encode dates.
What users feel
Higher code plus matching signature: Android installs it as an update and keeps data, databases and settings. Lower code: the install is refused — which is why sideloading an old APK over a newer one fails with a downgrade or parse error. Uninstalling first works but erases the app's data.
The signature half is not optional. An APK signed with a different key can never update an existing install, whatever the codes say; Android treats it as a different app with the same name.
Habits that avoid the error
- Bump the code in the same commit as the change, not at upload time from memory.
- Never reuse a code after a failed upload — Play may have recorded it. Increment and move on.
- If you use per-ABI APK splits, multiply the logical code by 10 to leave room in the units digit.
- Keep the name something you can say out loud; it turns up in reviews and support threads.