minSdk, targetSdk y compileSdk
Los tres numeros del fichero de compilacion que mas confusion generan y que en realidad responden a tres preguntas completamente distintas: contra que superficie compilo, que comportamientos del sistema acepto, y cual es el dispositivo mas antiguo al que llego. Esta leccion separa las tres responsabilidades una por una, explica el mecanismo real de los cambios de comportamiento condicionados por targetSdk y como se activan y desactivan uno a uno para probarlos, recorre el caso de estudio del borde a borde que Android 15 impuso y Android 16 dejo sin escapatoria, y termina con un criterio concreto para elegir los tres valores en 2026 sin copiar lo que haga el proyecto de al lado.
Tres líneas del fichero de compilación deciden más sobre el comportamiento de tu app que casi cualquier otra cosa que escribas, y sin embargo se copian de un proyecto a otro sin entenderlas. No son tres formas de decir lo mismo ni una escalera de valores relacionados: son tres preguntas distintas dirigidas a tres destinatarios distintos. Una se la haces al compilador, otra al sistema operativo del usuario y la tercera a la tienda de aplicaciones. Confundirlas produce los dos errores clásicos del oficio: subir el que no toca y romper la app en producción, o dejarlos congelados durante años hasta que un plazo de Play te obliga a saltar tres versiones de golpe en una tarde. Entender qué controla cada uno convierte una decisión supersticiosa en una decisión de ingeniería.
- Separar qué controla
compileSdk,targetSdkyminSdk, uno por uno. - Entender el mecanismo de los cambios de comportamiento condicionados por
targetSdk. - Probar y depurar esos cambios activándolos y desactivándolos a mano.
- Elegir los tres valores en 2026 con un criterio propio y defendible.
Tres números, tres preguntas
La confusión nace de que los tres se escriben juntos y llevan la palabra Sdk. Lo que hace cada uno no tiene nada que ver con lo que hacen los otros dos.
La forma más rápida de fijarlos es asociarlos a su destinatario, porque cada uno le habla a alguien distinto.
| Número | Pregunta que responde | Quién lo lee |
|---|---|---|
compileSdk |
Contra qué superficie de APIs compilo | El compilador, en tu máquina |
targetSdk |
Qué comportamientos del sistema acepto | El sistema operativo del usuario |
minSdk |
Cuál es el dispositivo más antiguo al que llego | La tienda y las herramientas de compilación |
android {
compileSdk = 37 // veo toda la API de Android 17 al compilar
defaultConfig {
minSdk = 26 // el suelo: no instalo por debajo de Android 8
targetSdk = 36 // acepto los comportamientos de Android 16
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}
compileSdk no viaja al dispositivo: solo determina qué clases y métodos ve tu código al compilar. Subirlo es barato y casi siempre correcto, porque te permite usar las anotaciones nuevas, ver los avisos de obsolescencia con antelación y compilar dependencias que ya apuntan a la última versión. minSdk sí tiene consecuencias medibles: marca el suelo de instalación, condiciona qué APIs puedes llamar sin protección, y activa o desactiva mecanismos de traducción como el azucarado de la biblioteca base, que hace disponibles las APIs modernas de fechas en dispositivos que no las traían. targetSdk es el interesante, y merece su propia sección.
Hay un cuarto actor que no aparece en el fichero y que conviene tener presente: tus dependencias. Cada biblioteca declara su propio minSdk, y el resultado efectivo de tu app es el mayor de todos, no el que tú escribiste. Si una dependencia sube su suelo, el tuyo sube con ella aunque no toques nada, y lo descubrirás como un error de fusión del manifiesto en la siguiente actualización. La lectura correcta de ese error no es forzar una anulación para silenciarlo, porque eso solo cambia el momento en que la app falla: es decidir conscientemente si subes el suelo o si te quedas en la versión anterior de esa biblioteca.
Se debe cumplir siempre minSdk menor o igual que targetSdk, y targetSdk menor o igual que compileSdk. Compilar contra una superficie más antigua que la que declaras aceptar no tiene sentido, y la herramienta de compilación te lo impedirá. Lo que sí es una decisión tuya es cuánta distancia dejas entre compileSdk y targetSdk: esa holgura es precisamente el espacio donde adoptas APIs nuevas sin haber asumido todavía los cambios de comportamiento que las acompañan.
targetSdk: el interruptor de los cambios de comportamiento
Aquí está la idea que hay que interiorizar. Cuando Android cambia un comportamiento que podría romper apps existentes, no lo aplica a todo el mundo: lo condiciona al targetSdk que la app declara. El sistema mira ese número y decide, para cada cambio individual, si te aplica la conducta nueva o te mantiene la antigua. Es decir, targetSdk es una declaración jurada: he probado mi app contra los comportamientos de esta versión y me hago responsable.
Nótese lo que eso implica: en un mismo dispositivo, dos apps pueden estar viviendo comportamientos del sistema distintos al mismo tiempo, según lo que cada una declare. Android no ejecuta un solo conjunto de reglas, ejecuta tantas variantes como valores de targetSdk haya instalados. Esa es la razón de que el sistema arrastre código de compatibilidad para versiones de hace una década, y también la razón de que la plataforma pueda cambiar de verdad sin dejar a nadie tirado.
Ese mecanismo está expuesto y se puede consultar y manipular, lo que lo convierte en una herramienta de prueba de primer orden. Cada cambio tiene un identificador, y se puede forzar activado o desactivado para una app concreta sin tocar el fichero de compilación.
# Ver el estado de todos los cambios de comportamiento para una app
adb shell dumpsys platform_compat | grep -i com.ejemplo.app
# Activar un cambio concreto aunque tu targetSdk todavia no lo active
adb shell am compat enable NOMBRE_DEL_CAMBIO com.ejemplo.app
# Desactivarlo para aislar si un fallo viene de ahi
adb shell am compat disable NOMBRE_DEL_CAMBIO com.ejemplo.app
# Restaurar el comportamiento que corresponde a tu targetSdk
adb shell am compat reset-all com.ejemplo.app
Con esas cuatro órdenes, subir de targetSdk deja de ser un salto al vacío y pasa a ser un procedimiento: activas los cambios uno a uno sobre la versión actual de tu app, ves cuál rompe qué, lo arreglas, y solo cuando la lista está limpia subes el número. Es exactamente lo contrario de lo que hace casi todo el mundo, que es subir el número y esperar a que los informes de fallos digan algo.
Hay además una asimetría que conviene tener clara antes de decidir cuándo subir. Los cambios condicionados por targetSdk no son la única forma en que el sistema evoluciona: hay cambios que se aplican a todas las apps sin importar lo que declaren, normalmente cuando afectan a la seguridad o a la privacidad de forma que no admite prórroga. Declarar un targetSdk bajo no te protege de esos, solo de los condicionados. Es decir, quedarse atrás no congela el mundo; congela una parte del mundo y te deja igual de expuesto a la otra, con la diferencia de que ya no puedes publicar correcciones.
flowchart TD A[El sistema va a aplicar un cambio de comportamiento] A --> B[Mira el targetSdk declarado por la app] B --> C[Es igual o mayor que la version del cambio] C -->|si| N[Comportamiento nuevo] C -->|no| V[Comportamiento antiguo por compatibilidad] N --> R[Salvo que se fuerce con el marco de compatibilidad] V --> R style N fill:#a6e3a1,color:#11111b style V fill:#f9e2af,color:#11111b style R fill:#89b4fa,color:#11111b
El caso del borde a borde: una escalera en tres peldaños
El ejemplo más didáctico de los últimos años es el dibujado de borde a borde, porque muestra el ciclo completo de cómo Android impone un cambio. Primer peldaño: durante años fue una opción que activabas tú si querías que tu contenido pasara por debajo de las barras del sistema. Segundo peldaño: Android 15 lo impuso a las apps que declaraban targetSdk 35, pero dejó una válvula de escape en el manifiesto para desactivarlo mientras adaptabas la interfaz. Tercer peldaño: Android 16 eliminó esa válvula, que quedó sin efecto para quien declare targetSdk 36.
Ese mismo patrón se está repitiendo ahora mismo con la interfaz adaptativa, y merece la pena verlo en marcha porque es el cambio que más código va a mover en los próximos dos años. Android 16 empezó a ignorar las restricciones de orientación y de redimensionado que una app declara, en pantallas de sw 600dp o más, para quien apunte a targetSdk 36; dejó una propiedad de escape en el manifiesto para desactivarlo temporalmente; y Android 17 la ha retirado. Si reconoces la forma del ciclo, sabes exactamente en qué peldaño estás y cuánto tiempo te queda.
La lección no es sobre insets, es sobre el patrón. Android casi nunca rompe de golpe: introduce, condiciona al targetSdk, ofrece una salida temporal y la retira una o dos versiones después. Quien usa la salida temporal como solución permanente descubre el problema el día en que le toca subir por obligación, con el plazo encima. Quien la usa como lo que es —tiempo comprado para migrar— llega al peldaño siguiente sin enterarse.
compileSdk alto, sin miedo
Súbelo pronto: no cambia nada en el dispositivo, te da acceso a las APIs nuevas y te enseña los avisos de obsolescencia con un año de antelación.
targetSdk con pruebas
Súbelo cuando hayas repasado la lista de cambios de comportamiento de esa versión. El plazo de Play marca la fecha límite, no el momento correcto.
minSdk con datos
No lo elijas por intuición ni copiando a otro proyecto: mídelo con la distribución real de tus usuarios, que verás en la lección siguiente.
Elegirlos en 2026
Con la mecánica clara, queda la parte que de verdad te van a preguntar en una revisión de código: qué números concretos poner hoy y con qué argumento.
Con Android 17 publicado en junio de 2026 y el plazo de Play del 31 de agosto exigiendo targetSdk 36 para toda publicación nueva o actualizada, la combinación sensata para un proyecto que empieza hoy es compileSdk 37, targetSdk 36 y un minSdk entre 24 y 26. La holgura entre compilar contra 37 y declarar 36 es deliberada: te da la superficie completa de la versión nueva mientras terminas de validar sus cambios de comportamiento, entre ellos el más notable de Android 17, que retira la posibilidad de que una app se niegue a redimensionarse o a girar en pantallas grandes de sw 600dp o más.
Un matiz que ahorra sustos: targetSdk no tiene por qué subir a la vez en todas las variantes de tu proyecto. Puedes mantener una variante interna que apunte ya a la versión nueva y distribuirla solo al equipo, mientras producción sigue en la anterior. Es la forma más barata de convertir la migración en algo continuo en lugar de en un evento, y aprovecha el sistema de variantes que estudiarás en el nivel de Gradle.
// Una variante interna que va por delante para detectar roturas pronto.
flavorDimensions += "canal"
productFlavors {
create("produccion") { targetSdk = 36 }
create("interna") { targetSdk = 37 }
}
El minSdk es la única de las tres que tiene un coste directo en usuarios, y por eso es la única que exige datos. Cada peldaño que subes elimina dispositivos reales; cada peldaño que no subes te obliga a mantener ramas de compatibilidad, arrastra bibliotecas antiguas y engorda el binario. La cifra de 24 a 26 no es dogma: es donde suele caer el punto en que el coste de mantener lo viejo supera al de perder esa cola, pero el punto exacto depende de tu mercado, y en un producto orientado a mercados emergentes puede estar claramente más abajo.
Conviene además saber qué se compra exactamente en cada peldaño, porque hay saltos que valen mucho más que otros. Subir a 24 desaparece toda una generación de problemas de multiproceso y de permisos. Subir a 26 te da los canales de notificación como algo garantizado y limpia buena parte del código de compatibilidad de arranque. Subir a 28 hace obligatorio el tráfico cifrado por omisión. Cada uno de esos peldaños elimina ramas de código, no solo dispositivos, y esa es la parte del cálculo que casi nadie pone en la balanza cuando solo mira el porcentaje que pierde.
Un minSdk bajo no aparece como una línea de gasto, aparece repartido: comprobaciones de versión salpicadas por toda la base de código, bibliotecas ancladas en versiones antiguas porque las nuevas exigen más, pruebas que hay que ejecutar en más configuraciones y una superficie mayor donde puede fallar algo. Ese gasto es real y crece con el tamaño del proyecto, solo que nadie lo factura. Ponerlo en el mismo lado de la balanza que el porcentaje de usuarios es lo que convierte la decisión en una comparación honesta.
Merece la pena detenerse en la elegancia del diseño, porque no hay tantos sistemas que hayan resuelto este problema. Android tiene que evolucionar —cerrar agujeros de privacidad, imponer límites de energía, cambiar cómo se dibuja una ventana— sobre una base instalada de miles de millones de dispositivos ejecutando apps cuyos autores, en muchos casos, ya no existen. Romper esas apps no es una opción, y congelar la plataforma tampoco. La solución fue condicionar cada cambio a una declaración de la propia app: dime contra qué versión has probado, y te daré exactamente ese mundo. Eso convierte targetSdk en algo mucho más profundo que un ajuste de compilación: es una firma. Al escribir 36 estás afirmando ante el sistema operativo que conoces los cambios de comportamiento de Android 16 y que tu código los soporta, y el sistema te cree y te los aplica todos. Por eso subirlo sin leer la lista de cambios es una negligencia con consecuencias, y por eso la solución al miedo no es no subirlo nunca —eso solo aplaza el impacto y lo concentra— sino tener el procedimiento: leer la lista, activar cada cambio a mano con el marco de compatibilidad, comprobarlo, arreglarlo y solo entonces firmar. Quien trabaja así ve las subidas de targetSdk como una tarea de una tarde. Quien no, las ve como una crisis anual con un plazo de Play respirándole en la nuca, y esa diferencia no es de talento ni de herramientas: es de haber entendido qué significa cada uno de estos tres números.
- Abre el fichero de compilación de un proyecto real y escribe, para cada uno de los tres valores, una frase que explique por qué tiene ese número y no otro. Si no puedes escribirla, ese es el problema.
- Ejecuta la orden que vuelca el estado del marco de compatibilidad para tu app y cuenta cuántos cambios de comportamiento hay activados y cuántos desactivados por tu
targetSdkactual. - Elige uno de los desactivados, actívalo a mano con
am compat enable, usa la app y anota qué se rompe. Después restáuralo todo. - Sube
compileSdka la última versión sin tocartargetSdky recompila: recopila la lista de avisos de obsolescencia nuevos, que es tu agenda de trabajo del año que viene. - Simula una bajada de
minSdkde dos niveles y observa qué bibliotecas dejan de resolverse o qué código pasa a exigir protección. Estima el coste real de esa cola de dispositivos.