minSdk, targetSdk, compileSdk
Three numbers, sitting three lines apart in the same file, meaning three completely different things. Learn which one decides what you may type, which one decides who can install your app, and which one is a promise about behaviour.
Three numbers, three different jobs
Open app/build.gradle.kts in any Android project and you find this:
1compileSdk = 35
2minSdk = 26
3targetSdk = 35Three settings. Two of them are the same number. All three contain the letters "sdk". They are almost universally misunderstood, and getting them muddled produces errors that seem to have nothing to do with the thing you changed.
Here is the whole lesson in three lines. Everything else is elaboration.
| Setting | The question it answers | Ours |
|---|---|---|
| compileSdk | Which Android functions am I allowed to type? | 35 |
| minSdk | What is the oldest phone that may install this? | 26 |
| targetSdk | Whose rules do I promise to follow? | 35 |
Think about writing a cookery book.
compileSdk is the reference library you are allowed to consult while writing. A bigger library means more techniques you are permitted to mention. It affects the writing only — no reader ever sees it, and consulting a 2024 book does not stop a 1990s cook using your recipes.
minSdk is the line on the cover: "works in any kitchen with a hob and an oven." Set it low and more people can cook from your book, but you must avoid mentioning anything they might not own. Set it high and you can assume a sous-vide machine.
targetSdk is the edition you tested in: "tested against the 2025 kitchen standard." It is a promise. Say 2025 and the safety inspector holds you to the 2025 rules. Say 2019 and the inspector quietly applies the 2019 rules instead, to be kind to an old book — but eventually stops stocking books that old.
Where they live
API levels
Before going further: Android versions have names and numbers, and code always uses the number. An is just a counter that goes up with each release.
| API level | Android | Released |
|---|---|---|
| 26 | 8.0 Oreo | 2017 |
| 29 | 10 | 2019 |
| 31 | 12 | 2021 |
| 33 | 13 | 2022 |
| 34 | 14 | 2023 |
| 35 | 15 | 2024 |
So minSdk = 26 means "Android 8.0 Oreo and up", and compileSdk = 35 means "the Android 15 library".
compileSdk — what you may type
compileSdk picks which version of the Android code library the checks your code against. That is its entire job.
It is a build-time setting. It is not written into the APK. It does not appear in the manifest. No phone ever sees it.
Two consequences that beginners find surprising:
- Compiling against 35 does not mean your app needs Android 15. It only means you were allowed to type function names that exist in Android 15.
- Raising it never breaks old phones. It can, however, produce new warnings, because a newer library knows about more things that are .
The rule of thumb is simple: use the newest compileSdk your build tools support. For AGP 8.7.3 that is 35, which is what this course uses. Libraries increasingly demand it — if you use a library built against 35 while you are on 34, the build stops before it starts.
minSdk — who may install
minSdk is a gate. Google Play will not offer your app to a phone below it, and a direct install is refused outright with INSTALL_FAILED_OLDER_SDK.
The trade-off is real and goes both ways.
Lower minSdk reaches more phones — but every function newer than that level becomes a problem you have to handle by hand. Type one and the build tools stop you:
Field requires API level 33 (current min is 26)
The fix is a version check at run time:
1val v = Build.VERSION.SDK_INT
2if (v >= Build.VERSION_CODES.TIRAMISU) {
3 // Only runs on Android 13 and newer.
4 askForNotificationPermission()
5}Inside that if, the tools relax, because they can prove the code cannot run on an older phone.
Higher minSdk means fewer of those checks, at the cost of leaving people behind.
Why this course uses 26
minSdk = 26 covers roughly 95% of Android phones in use, and it buys three specific things:
- Java's modern date and time classes work without — an extra build step that would slow every build on your phone.
- are supported, so Lesson 6.1 can draw a proper modern icon.
- Notification channels exist, which Focus Flow needs in Lesson 5.17.
Every one of those would otherwise be an if and a fallback. Choosing 26 removes them all.
targetSdk — the promise
This is the one that genuinely confuses people, because it does nothing at build time. It is read by Android itself, at run time, on the user's phone.
Each Android release changes some behaviour in a way that could break existing apps. Rather than breaking them, Android checks your targetSdk and asks: does this app claim to know about this change?
- If your
targetSdkis at or above that release, you get the new behaviour. - If it is lower, Android quietly keeps the old behaviour for you.
That is why targetSdk is a promise rather than a request. You are saying: I have tested on this version and I accept its rules.
Three concrete examples, all real:
| targetSdk | What changes for your app | |
|---|---|---|
| 33+ | Notifications require asking the user for permission. Below 33 you were simply granted it. | |
| 34+ | Foreground services must declare what kind of work they do. | |
| 35+ | Your app draws [[edge-to-edge | edge to edge]] on Android 15 by default — content goes under the status bar unless you handle insets. |
That last one catches people out badly. Raise targetSdk to 35, and a title that used to sit neatly below the clock is suddenly behind it. Nothing failed; you simply took on a new responsibility.
targetSdk is not optional in practice. Google Play requires new apps and updates to target a recent Android version, and that requirement rises every year. Staying on an old targetSdk to avoid the work eventually means being unable to publish at all. Lesson 6.7 covers what Play asks for.
The rule that ties them together
minSdk <= targetSdk <= compileSdkYou cannot promise to behave on a version you were not allowed to compile against. You cannot target something older than your own minimum. In practice almost every app sets targetSdk and compileSdk to the same, newest number, and minSdk as low as it can bear.
- Open Pocket Studio, tap Projects, open your Part 0 app.
- Tap Editor, open the file tree, and open
app/build.gradle.kts. - Find
compileSdk,minSdkandtargetSdk. Say out loud what each one controls before reading on. Type, install, promise. - Change
minSdk = 26tominSdk = 35. Tap Build and press Run. - If your phone runs Android 15 or newer, it installs fine — the gate lets it through. If your phone is older, read the failure carefully: it is
INSTALL_FAILED_OLDER_SDK, and it is the gate doing exactly its job. - Put it back to 26 and press Run again.
- Now open the Terminal in the Build tab and run
./gradlew :app:assembleDebug --warning-mode all. Any warnings you see come from compiling against 35 — proof thatcompileSdkaffects the build and nothing else.
compileSdk to at least the number in the message. Raising compileSdk never affects which phones can install your app, so this is almost always safe.minSdk version of Android was released. On an Android 8 phone that name is simply not there, so the app would crash.if (Build.VERSION.SDK_INT >= 33) { … }, and write a sensible fallback for older phones. Raising minSdk also silences it, at the cost of dropping devices.minSdk is higher than the Android version on the phone you are installing to. The gate did what it is for.minSdk in app/build.gradle.kts to a value your phone meets — 26 for this course — and build again.android { } block has no compileSdk line at all, so the plugin does not know which Android library to compile against. Usually a line deleted by accident, or a merge that went wrong.compileSdk = 35 as the first line inside android { }, above defaultConfig. It goes outside defaultConfig, not inside it.agp version in gradle/libs.versions.toml.- decides what you may type. Build-time only, never shipped, never affects who can install. Use the newest your tools support — 35 here.
- decides who may install. Lower reaches more phones and costs you version checks. This course uses 26 — Android 8.0, about 95% of devices.
- is a promise about behaviour, read by Android at run time. Raising it opts you into new rules like drawing.
- Always
minSdk <= targetSdk <= compileSdk. - are numbers; Android versions are names. Code always uses the number.
- Next: the file all of this produces — what is really inside an APK, entry by entry.