microg/GmsCore · error · RuntimeException

Error writing

Error message

Error writing %s

What it means

SafeParcelProcessor's generated `writeToParcel` wraps variable declaration and field-to-parcel writing in try/catch and rethrows as RuntimeException("Error writing <class>", e). It marks which @SafeParcelable class failed to serialize; the cause reveals the offending field (unsupported type, throwing getter, null that cannot be written, etc.).

Solutions

  1. Read the cause stack trace to identify the failing field
  2. Ensure every @Field has a SafeParcelWriter-supported type and is initialized (non-null or explicitly nullable-safe) before writeToParcel
  3. Fix or remove the nested object that throws during its own writeToParcel
  4. Rebuild the project so generated writer code matches the current class definition

Example fix

// before: uninitialized field blows up on write
@Field(value = 2)
lateinit var config: MyConfig
// after: write nullable with default
@Field(value = 2)
var config: MyConfig? = null
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure all fields are initialized before handing the object to a Parcel
require(!requiresInit || ::config.isInitialized) { "config must be set before parceling" }

Type guard

fun MySafeParcelable.isWritable(): Boolean =
  this::class.declaredMemberProperties.all { p -> (p.getter.call(this) as? Throwable)?.let { false } ?: true }

Try / catch

try {
  dest.writeTypedObject(item, flags)
} catch (e: RuntimeException) {
  if (e.message?.startsWith("Error writing") == true) {
    Log.w(TAG, "SafeParcel write failed", e.cause)
    dest.writeNoOp() // or serialize a fallback representation
  } else throw e
}

Prevention

When it happens

Trigger: Any exception while writing fields of a @SafeParcelable object: a field getter throws, a field value's own writeToParcel fails (nested SafeParcelable/Parcelable with problems), or a field type cannot be represented by SafeParcelWriter.

Common situations: Nested Parcelable whose own serialization breaks; lazily-initialized field that is null or throwing at write time; class refactored with a field type the SafeParcel writer cannot handle; transaction stopped mid-write (rare — usually surfaces differently).

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/db9ff028ec2d9bd5. Report an issue: GitHub.

Appendix: source

Thrown at safe-parcel-processor/src/main/kotlin/org/microg/safeparcel/SafeParcelProcessor.kt:184

                            $setFieldsFromVariables
                        } catch (Exception e) {
                            throw new RuntimeException(String.format("Error reading %s", "$fullName"), e);
                        }
                        if (parcel.dataPosition() > end) {
                            throw new RuntimeException(String.format("Overread allowed size end=%d", end));
                        }
                        return object;
                    }

                    @Override
                    public void writeToParcel($fullName object, $Parcel parcel, int flags) {
                        int start = $SafeParcelWriter.writeObjectHeader(parcel);
                        try {
                            $variableDeclarations
                            $setVariablesFromFields
                            $writeVariableToParcel
                        } catch (Exception e) {
                            throw new RuntimeException(String.format("Error writing %s", "$fullName"), e);
                        }
                        $SafeParcelWriter.finishObjectHeader(parcel, start);
                    }

                    @Override
                    public $fullName[] newArray(int size) {
                        return new $fullName[size];
                    }
                }
            """.trimIndent()
        return file
    }
}

class ConstructorInfo(val clazz: ClassInfo, val constructorElement: ExecutableElement) {
    val isPrivate by lazy { constructorElement.modifiers.contains(Modifier.PRIVATE) }
    val parameters by lazy { constructorElement.parameters }
    val fieldIds by lazy {

View on GitHub (pinned to 157c9d86ac)