Question
Fix PendingIntent FLAG_IMMUTABLE or FLAG_MUTABLE Error in MediaSessionCompat on Android 12+
Question
I am updating an Android app to target SDK 31 (Android 12) and I am getting an error when creating a MediaSessionCompat inside a service that extends MediaBrowserServiceCompat.
My service initializes the media session in onCreate() like this:
override fun onCreate() {
super.onCreate()
mediaSession = MediaSessionCompat(this, TAG).apply {
setCallback(mediaSessionCallback)
isActive = true
}
}
At runtime, the app crashes with this exception:
java.lang.RuntimeException: Unable to create service com.radio.core.service.MediaService: java.lang.IllegalArgumentException: com.xxx.xxx: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
The stack trace shows the crash happens inside MediaSessionCompat while it creates a PendingIntent internally.
I am already using a recent media dependency:
implementation("androidx.media:media:1.4.0")
In the library source, I can see logic related to mutable PendingIntent flags, for example:
public static final int PENDING_INTENT_FLAG_MUTABLE =
Build.VERSION.CODENAME.equals("S") ? 0x02000000 : 0;
and later:
mbrIntent = PendingIntent.getBroadcast(
context,
0,
mediaButtonIntent,
PENDING_INTENT_FLAG_MUTABLE
);
Why does this still fail when targeting Android 12+, and how can it be fixed correctly?
Short Answer
By the end of this page, you will understand why Android 12+ requires explicit PendingIntent mutability flags, why older MediaSessionCompat versions can still crash even if they appear to support Android S, and how to fix the issue by updating dependencies or creating PendingIntents correctly in Android apps.
Concept
Android 12 (API 31, also called Android S) introduced a stricter rule for PendingIntent: you must explicitly declare whether it is mutable or immutable.
A PendingIntent is a token that lets another part of Android, or another app, perform an action later on your app's behalf. Because this can affect security, Android now requires developers and libraries to state whether that token can be modified.
The two important flags are:
PendingIntent.FLAG_IMMUTABLE— the wrapped intent cannot be changed later.PendingIntent.FLAG_MUTABLE— the wrapped intent can be modified later if required.
In most cases, FLAG_IMMUTABLE is the safer choice.
The problem in this question is slightly tricky because the crash is not coming from your own code directly. It is happening inside MediaSessionCompat, which internally creates a PendingIntent for media button handling.
Why this happens:
- Your app targets API 31 or above.
- Android enforces explicit mutability flags.
- An older library version may try to work around Android S using preview-only checks such as
Build.VERSION.CODENAME.equals("S"). - Once Android 12 became final, that logic became unreliable because production devices report
Build.VERSION.SDK_INT >= 31, not preview codename logic in the same way the library expected.
So even though the library source seems to mention Android S support, that specific version may still not be fully compatible for released Android 12 behavior.
This matters in real programming because platform behavior changes often affect support libraries, notifications, services, alarms, deep links, and media components. When you target a newer SDK, both your app code and your dependencies must follow the new rules.
Mental Model
Think of a PendingIntent like a sealed or unsealed envelope that you hand to the Android system.
- Immutable means the envelope is sealed. The system can deliver it, but cannot change what is inside.
- Mutable means the envelope is openable. The system or another component may add or modify details before using it.
Android 12 now says: you must label the envelope clearly.
If you do not say whether it is sealed or unsealed, Android rejects it with an exception.
In this case, MediaSessionCompat is creating the envelope for you. If the library forgets to label it properly, your app still crashes even though your own service code looks correct.
Syntax and Examples
When creating a PendingIntent on Android 12+, always include either FLAG_IMMUTABLE or FLAG_MUTABLE.
Basic syntax
val pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
Or, if mutability is required:
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_MUTABLE
)
Safer cross-version pattern
If your code must support older Android versions too, combine flags carefully:
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
val pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
flags
)
Example in a notification
val openAppIntent = Intent(this, MainActivity::class.java)
val openAppPendingIntent = PendingIntent.getActivity(
this,
0,
openAppIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
This example works because:
FLAG_UPDATE_CURRENTupdates an existing pending intent if one exists.FLAG_IMMUTABLEsatisfies Android 12+ requirements.- The intent does not need to be changed later, so immutable is appropriate.
In the MediaSessionCompat case
If the crash is inside the library, you usually cannot fix it by changing only your onCreate() code. The correct fix is often to update to a library version that properly uses FLAG_MUTABLE or FLAG_IMMUTABLE internally.
Step by Step Execution
Consider this example:
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
100,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
Here is what happens step by step:
Intent(this, MainActivity::class.java)creates an intent describing the action: openMainActivity.PendingIntent.getActivity(...)asks Android to create a reusable token for launching that activity later.- Request code
100helps Android identify this pending intent. FLAG_UPDATE_CURRENTtells Android to update the existing matching pending intent if one already exists.FLAG_IMMUTABLEtells Android that the intent contents must not be modified later.- On Android 12+, the system accepts this because mutability has been explicitly declared.
Now compare that with broken code:
val pendingIntent = PendingIntent.getActivity(
this,
100,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
On Android 12+:
- Android sees that a
PendingIntentis being created. - It checks whether
FLAG_IMMUTABLEorFLAG_MUTABLEis present. - Neither flag exists.
- Android throws
IllegalArgumentException. - If this happens during service creation, the service crashes before finishing
onCreate().
That is exactly the kind of failure seen in the MediaSessionCompat stack trace.
Real World Use Cases
PendingIntent mutability matters in many common Android features:
-
Notifications
- Opening an activity when a notification is tapped
- Triggering action buttons like Reply, Pause, or Dismiss
-
Media playback
- Media session controls
- Media button events from headphones, car systems, or lock screen controls
-
Alarms and scheduled work
AlarmManagercallbacks- Deferred actions triggered by the system
-
Broadcast receivers
- Delivering future intents to a receiver
- Background event handling
-
Foreground services
- Notification actions tied to service behavior
In media apps specifically, the support library may create internal PendingIntents for transport controls and media button integration. That is why dependency compatibility is so important when upgrading target SDK versions.
Real Codebase Usage
In real Android projects, developers usually handle this concept in a few practical ways.
1. Prefer immutable by default
Most app-generated PendingIntents do not need to be changed later.
val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
This is the most common pattern for notifications and launch intents.
2. Use mutable only when required
Use FLAG_MUTABLE only for cases where Android or another component must modify the intent.
Examples include:
- inline replies in notifications
- bubbles
- some remote input flows
- framework features that explicitly require mutable intents
3. Centralize flag logic
Many codebases create helper functions so all PendingIntents follow the same rules.
fun defaultPendingIntentFlags(): Int {
return PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
}
This avoids repeated mistakes.
4. Update dependencies when platform rules change
If a crash comes from AndroidX or another library, application code may not be enough. Real projects often fix this by:
- updating AndroidX dependencies
- checking release notes
- cleaning Gradle caches
- verifying no older transitive dependency is overriding the newer one
5. Use guard checks when needed
For custom code that must behave differently on different Android versions:
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
This is useful for app code, though many flag constants are safe to combine on older versions depending on the constant and minimum SDK.
6. Watch for transitive dependency conflicts
A real project may declare androidx.media:media:1.4.0, but another dependency may pull in an older version at runtime or compile time. Developers often confirm this with Gradle dependency inspection.
Common Mistakes
1. Assuming the crash must be in your own code
In this question, your service code is simple and looks valid:
mediaSession = MediaSessionCompat(this, TAG)
But the real failure happens inside the library when it creates a PendingIntent.
How to avoid it
- Read the stack trace carefully.
- Look at the deepest library call before the exception.
- Check dependency versions.
2. Using no mutability flag on Android 12+
Broken example:
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
Fix
PendingIntent.getBroadcast(
this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
3. Using FLAG_MUTABLE everywhere
Some developers fix the crash by always using mutable.
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
This may work, but it is not the safest default.
Better approach
- Use
FLAG_IMMUTABLEunless you truly need mutability. - Use
FLAG_MUTABLEonly for features that require modification.
4. Trusting preview SDK checks like Build.VERSION.CODENAME == "S"
Broken idea:
Build.VERSION.CODENAME.equals("S")
This kind of logic was used during preview support but is not the correct long-term condition for released Android versions.
Better approach
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
Or even better, rely on a library version that already handles it correctly.
5. Updating one dependency but still resolving an older one
You may declare a newer dependency, but Gradle might still resolve a different version because of dependency constraints.
How to avoid it
- Run a dependency tree task.
- Check the resolved version, not just the declared version.
- Force or align dependency versions if necessary.
6. Forgetting to clean and rebuild after dependency updates
Android Studio and Gradle caches can hide fixes temporarily.
Good practice
- Sync Gradle
- Clean project
- Rebuild
- Reinstall the app
Comparisons
| Topic | What it means | When to use it | Notes |
|---|---|---|---|
FLAG_IMMUTABLE | The PendingIntent cannot be changed after creation | Default choice for most notifications, launches, and broadcasts | Safer and recommended unless mutability is required |
FLAG_MUTABLE | The PendingIntent can be modified later | Inline replies, bubbles, or framework cases that require changes | Use only when necessary |
| No mutability flag | No explicit mutability declared | Never on Android 12+ targets | Causes IllegalArgumentException |
| Approach | Pros | Cons |
|---|---|---|
Fix your own PendingIntent code | Simple when the issue is in app code | Does not help if the crash is inside a library |
| Update AndroidX/media library | Correct fix for library-internal crashes | Requires checking compatible versions |
| Patch around with older target SDK | Temporary workaround only | Avoids the real issue and delays required migration |
| Version check style | Recommendation | Why |
|---|---|---|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S | Good | Works for released Android versions |
Build.VERSION.CODENAME.equals("S") | Avoid for final app logic | Preview-oriented and unreliable for long-term released behavior |
Cheat Sheet
PendingIntent rules on Android 12+
- Always specify one of:
PendingIntent.FLAG_IMMUTABLEPendingIntent.FLAG_MUTABLE
- Prefer
FLAG_IMMUTABLEby default. - Use
FLAG_MUTABLEonly when required.
Common patterns
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
Choose the right flag
-
Use immutable for:
- opening an activity
- standard notification taps
- most broadcasts
- fixed service intents
-
Use mutable for:
- inline reply actions
- bubbles
- cases where Android must fill in or modify extras later
For MediaSessionCompat crashes
- Check the stack trace.
- If the crash is inside
MediaSessionCompat, update the media library. - Make sure the resolved dependency version is actually updated.
- Do not rely on preview SDK codename checks.
Quick fix checklist
- Update AndroidX media dependency to a version that properly supports Android 12.
- Re-sync Gradle.
- Clean and rebuild.
- Verify no older transitive dependency is used.
- Add mutability flags to all of your own
PendingIntents too.
FAQ
Why does this error appear only after targeting SDK 31?
Because Android 12 enforces explicit PendingIntent mutability when your app targets API 31 or higher.
Should I use FLAG_IMMUTABLE or FLAG_MUTABLE?
Use FLAG_IMMUTABLE in most cases. Use FLAG_MUTABLE only when the PendingIntent must be modified later.
Can MediaSessionCompat create a PendingIntent internally?
Yes. That is why the crash can happen even if you did not manually create a PendingIntent in your own service code.
Why didn't androidx.media:media:1.4.0 fix it?
Some earlier support logic relied on preview Android S checks and was not sufficient for final Android 12 behavior. A newer compatible version is needed.
How do I know whether the crash comes from my code or a library?
Read the stack trace carefully. If the exception originates inside android.support.v4.media.session.MediaSessionCompat or androidx classes, the library is involved.
Can I work around this by lowering target SDK?
You could temporarily avoid the enforcement, but this is not a proper fix. The correct solution is to update code and dependencies.
How can I verify which dependency version Gradle is actually using?
Use Gradle dependency insight or dependency tree tasks to inspect the resolved version, not just the version you declared.
Mini Project
Description
Build a small Android utility object that creates safe PendingIntents for activities and broadcasts. This project demonstrates how to apply Android 12+ mutability rules consistently and avoid crashes caused by missing flags.
Goal
Create reusable helper functions that generate correct immutable or mutable PendingIntents for common app actions.
Requirements
- Create one helper function for launching an activity.
- Create one helper function for sending a broadcast.
- Use
FLAG_IMMUTABLEas the default behavior. - Allow a mutable option only when explicitly requested.
- Demonstrate usage with one activity intent and one broadcast intent.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.