Question

Fix PendingIntent FLAG_IMMUTABLE or FLAG_MUTABLE Error in MediaSessionCompat on Android 12+

kotlinjavaandroidandroid-studioandroid-mediaplayer

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.

Take Quiz

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_CURRENT updates an existing pending intent if one exists.
  • FLAG_IMMUTABLE satisfies 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:

  1. Intent(this, MainActivity::class.java) creates an intent describing the action: open MainActivity.
  2. PendingIntent.getActivity(...) asks Android to create a reusable token for launching that activity later.
  3. Request code 100 helps Android identify this pending intent.
  4. FLAG_UPDATE_CURRENT tells Android to update the existing matching pending intent if one already exists.
  5. FLAG_IMMUTABLE tells Android that the intent contents must not be modified later.
  6. 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+:

  1. Android sees that a PendingIntent is being created.
  2. It checks whether FLAG_IMMUTABLE or FLAG_MUTABLE is present.
  3. Neither flag exists.
  4. Android throws IllegalArgumentException.
  5. 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

    • AlarmManager callbacks
    • 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.

Take Quiz

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_IMMUTABLE unless you truly need mutability.
  • Use FLAG_MUTABLE only 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

TopicWhat it meansWhen to use itNotes
FLAG_IMMUTABLEThe PendingIntent cannot be changed after creationDefault choice for most notifications, launches, and broadcastsSafer and recommended unless mutability is required
FLAG_MUTABLEThe PendingIntent can be modified laterInline replies, bubbles, or framework cases that require changesUse only when necessary
No mutability flagNo explicit mutability declaredNever on Android 12+ targetsCauses IllegalArgumentException
ApproachProsCons
Fix your own PendingIntent codeSimple when the issue is in app codeDoes not help if the crash is inside a library
Update AndroidX/media libraryCorrect fix for library-internal crashesRequires checking compatible versions
Patch around with older target SDKTemporary workaround onlyAvoids the real issue and delays required migration
Version check styleRecommendationWhy
Build.VERSION.SDK_INT >= Build.VERSION_CODES.SGoodWorks for released Android versions
Build.VERSION.CODENAME.equals("S")Avoid for final app logicPreview-oriented and unreliable for long-term released behavior
Take Quiz

Cheat Sheet

PendingIntent rules on Android 12+

  • Always specify one of:
    • PendingIntent.FLAG_IMMUTABLE
    • PendingIntent.FLAG_MUTABLE
  • Prefer FLAG_IMMUTABLE by default.
  • Use FLAG_MUTABLE only 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

  1. Update AndroidX media dependency to a version that properly supports Android 12.
  2. Re-sync Gradle.
  3. Clean and rebuild.
  4. Verify no older transitive dependency is used.
  5. 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.

Take Quiz

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_IMMUTABLE as the default behavior.
  • Allow a mutable option only when explicitly requested.
  • Demonstrate usage with one activity intent and one broadcast intent.
Take Quiz

Keep learning

Related questions

View more