Android 17 — API 37, internal codename Cinnamon Bun — went stable on 16 June 2026, alongside the June Pixel Drop, on Pixel 6 and newer. Other OEMs have been joining the rollout since. The API surface locked at Beta 3, on 26 March, when Google declared platform stability; Beta 4, the last scheduled beta, shipped on 16 April. My Beta 3 write-up covers API 37 at platform stability; this covers what happened after it, and what actually costs you time.
Six weeks in, the useful framing is not the feature list. It is this: most of
what will break your app is dormant right now. It fires when you bump
targetSdk to 37, not when a user updates their phone. That means you choose the
day it happens, which is worth using deliberately.
The process changed, and that is the real news
For the first time in years, Google shipped no Developer Previews. Android 17 went straight to Beta.
That is not a shortcut. It is the Canary channel doing the job Developer Previews used to: APIs and features get tested continuously there, so a separate preview build stopped earning its place in the calendar. The consequence for planning is real — the window between “first buildable release” and “platform stability” is now shorter, and if your process assumed a DP phase to absorb surprises, that phase no longer exists.
What breaks on targetSdk 37
Large-screen resizability is no longer optional
Apps targeting Android 17 can no longer opt out of orientation, resizability and aspect-ratio constraints on displays whose smallest width is over 600dp. The manifest flags that used to pin an app to portrait stop being honoured, the app fills the whole display window, and there is no pillarboxing to hide behind.
This is the one to plan for, not patch. If your layout assumed a fixed orientation, it will be resized on a tablet or a foldable and you will find out where the assumptions live. Google frames Android 17 as the move to an “adaptive-first” standard, and this is the clause that enforces it.
The exemption is narrower than “games are fine”: it applies to apps that
declare android:appCategory="game". It is a manifest declaration, not a
vibe. Google has not announced the Play deadline for API 37 yet (as of
October 2026); the current rule asks for API 36 from 31 August 2026, and each
year so far has moved the bar one level. Plan as if API 37 follows — the
exemption buys layout work, not an escape.
System.load() requires read-only native libraries
All native files loaded using
System.load()must be marked as read-only. Otherwise, the system throwsUnsatisfiedLinkError.
If you ship native code that gets extracted, copied, or downloaded at runtime and then loaded from a writable location, that path now throws. This is a Dynamic Code Loading restriction: a library you can still write to is a library an attacker can still rewrite between the check and the load.
Worth grepping for System.load( — not loadLibrary, which resolves from the APK
and is unaffected.
Local network access is blocked by default
Apps targeting 17 have local network access blocked by default. mDNS, raw sockets, device discovery — anything that reaches a printer, a Chromecast, a smart bulb, a local dev server, or another phone on the same Wi-Fi.
There are two ways back in, and the order matters:
- A system-mediated device picker. The user chooses the device, the system grants the scope, and you never show a permission prompt at all.
- The new
ACCESS_LOCAL_NETWORKruntime permission, requested explicitly, for persistent LAN communication.
Reaching for the permission first is the reflex, and it is the worse option: you pay a prompt, a denial rate, and a justification you have to write. Try the picker before you decide you need blanket access.
Background audio needs a foreground service
On Android 17, every app — whatever it targets — needs a visible activity or a
running foreground service (not of type SHORT_SERVICE) to play audio,
request audio focus or change the volume. An app targeting 37 that does it from
the background must also meet one of two conditions:
- the foreground service has while-in-use (WIU) capabilities, or
- the app holds the exact alarm permission and is driving
USAGE_ALARMstreams.
Note what the platform actually demands — a foreground service with the right
capability. MediaSessionService is not in the rule; it is the practical way to
satisfy it. Media3 configures, starts and stops that service for you and publishes
the media notification, so an app built on MediaSessionService is mostly already
compliant.
Which makes this the deadline for ExoPlayer 2. It is unmaintained, it does not manage the foreground-service lifecycle, and the new rules are shaped around Media3’s session and focus handling. Nothing hard-blocks ExoPlayer 2 from running — you just get interrupted playback, lost focus and broken notifications, which is a worse failure than a crash because nobody files a bug for it.
Whatever the target, the failure mode is the same: audio APIs called outside a
valid lifecycle fail silently, and audio focus returns
AUDIOFOCUS_REQUEST_FAILED.
Reflection into the platform stops working
Two changes, one root cause, and they land on your dependencies rather than your code.
static final fields can no longer be changed. An app running on 17 and
targeting 37 that rewrites one by reflection gets an IllegalAccessException;
doing it through JNI (SetStaticLongField() and friends) crashes the process.
Test doubles, feature-flag shims and analytics SDKs do this more than you would
guess.
MessageQueue is a new lock-free implementation. The old one used a single
lock for the main thread’s task queue, so a background thread could block the main
one — contention that showed up as dropped frames. The rewrite removes it. The
cost is that anything reflecting into MessageQueue internals breaks: mMessages
still exists for binary compatibility, but it is always null, whether or not
messages are pending. Espresso and Robolectric are the ones you will hit first, so
update those libraries before you touch targetSdk.
Audit your dependency tree before the bump, not after. A silent null from a
field that used to hold data is the most expensive kind of failure to trace.
OTP SMS arrives three hours late
For most apps targeting 17, one-time-password SMS is not readable until three
hours after it arrives. During that window the SMS_RECEIVED_ACTION broadcast
is withheld and SMS provider queries are filtered. The default SMS app, the
assistant app and connected companion apps are exempt.
The same three-hour hold applies to WebOTP-format messages regardless of targetSdk, when the app is not the verified recipient by domain.
Three hours is not a delay you design around; it is a message that reading the inbox is over. If you still parse SMS directly, move to SMS Retriever or SMS User Consent — both hand you the code without the app reading the user’s messages, which is the point.
Certificate Transparency is on by default
CT was opt-in on Android 16. For apps targeting 17 it is enabled by default.
For almost everyone this is invisible and good. It bites in exactly one place: if you pin or inject a certificate that is not in a public CT log — a corporate proxy, a test harness, an internal CA — connections that used to work will fail. Check your staging environment before you assume production is fine.
Contacts get narrower
Two things moved, and only one of them breaks.
The breaking one: for apps targeting 37, Contacts Provider 2 hides PII columns
from the data view — ACCOUNT_NAME, ACCOUNT_TYPE, ACCOUNT_TYPE_AND_DATA_SET.
If you query them, plan for them to be gone.
The other is an opportunity. Android 17 ships a standardised Contact Picker: a
browsable system UI where you declare the fields you need — phone numbers, emails
— and the user hands you specific contacts. READ_CONTACTS is not deprecated, but
it is now the blunt option. If you asked for the whole address book because there
was no alternative, there is one now.
The garbage collector went generational
ART’s Concurrent Mark-Compact collector is now generational. Instead of treating every object the same and sweeping the whole heap, it runs frequent cheap young-generation collections and reserves full-heap passes for when they are actually needed.
It is not new in 17, though Google’s launch post presents it that way. Google
listed the generational CMC collector in the
Android 16 QPR2 release post
in December 2025, and AOSP agrees: the use_generational_cmc flag is first
ENABLED in the bp4a release configuration, the Android 16 QPR2 build
(BP4A.251205.006). Android 17’s configuration, cp1a, inherits from bp4a, so
17 keeps it on.
The premise is the oldest observation in garbage collection: most objects die young. Acting on it cuts GC CPU cost and collection duration, which shows up as fewer dropped frames and less battery burned doing bookkeeping.
You get this for free — no targetSdk, no code change. And because ART ships
through Google Play system updates, runtime improvements reach devices back to
API 31, not just the ones that took the OS upgrade.
Post-quantum signing is in Keystore
Android Keystore now supports ML-DSA — Module-Lattice-Based Digital Signature
Algorithm, the NIST post-quantum standard — in two variants, ML-DSA-65 and
ML-DSA-87.
The part that matters for adoption: it is exposed through the standard Java
Cryptographic Architecture APIs. KeyPairGenerator, KeyFactory, Signature.
No new surface to learn, no vendor SDK. If you generate signing keys in hardware
today, the shape of the code does not change — the algorithm string does.
Whether you need this yet is a separate question. Post-quantum signatures matter where a signature has to remain verifiable for years and an adversary can store today’s traffic to attack later. For a session token that expires in an hour, it does not. It is good that the platform is ready before the need is urgent, which is the correct order for cryptographic migrations.
Memory limits, briefly
Android 17 enforces per-app memory limits derived from device RAM, and a
process that exceeds them is killed with no stack trace. ApplicationExitInfo
is where you find out it happened.
This one deserves more than a paragraph, and it has its own part: Android 17 kills your process with no stack trace.
What users actually got
Two things worth knowing because support questions arrive about them:
App Bubbles for any app. Long-press an app on the home screen and it becomes a chat-head-style floating bubble. Previously bubbles were a messaging affordance; now any app can be one, which means your UI can be asked to render in a small floating window it was never designed for.
Find Hub “Mark as lost” can require biometrics. Locking a lost device can now demand biometric authentication in addition to the passcode, so someone who has watched you type your PIN still cannot disable tracking. A small change with a clear threat model behind it.
What I would do this week
- Compile against SDK 37 in CI, without changing
targetSdk. You get deprecation and API-surface signal at zero behavioural risk. - Audit your dependencies for reflection. Testing, feature-flag and analytics
libraries are the ones rewriting
static finalfields and readingMessageQueueinternals. Update Espresso and Robolectric first. - Grep for
System.load(. Fastest possible check for the breaking change most likely to be missed. - Test on a foldable or a tablet emulator in landscape. Not because it will look bad — because it will now happen whether or not you opted in.
- Inventory your permissions. Anything touching the LAN, the address book or the SMS inbox now has a narrower, system-mediated path that beats asking.
- Run your staging TLS path. CT-by-default fails loudly, but only where you have a non-public certificate, and that is usually not production.
- Then bump
targetSdk, deliberately, with time to react.
If you run a team, do not wait for the Play deadline to force the number. The audit is the slow part, not the bump.
The pattern in this release is consistent: the platform is closing paths that used to be optional — orientation opt-outs, writable native libraries, unrestricted LAN access, broad contact reads, SMS scraping, reflection into internals, unbounded memory. Each one was a place where an app could be careless and the cost landed somewhere else. That is a defensible direction. It also means “it still works” stops being evidence of anything the day you change one number.