Android properties look deceptively simple from a shell: getprop prints a value and setprop changes one. Underneath, the read path is optimized for many processes, while the write path crosses a protected service boundary. That asymmetry is the key to understanding why a property may be visible yet unwritable—and why adding one to a product is an API and SELinux design task, not merely editing build.prop.
Properties are strings with enforced contracts
The underlying store is a dictionary of property names and string values.
property_contextscan enforce bool, signed/unsigned integer, double, enum, or string syntax when values are set.Generated Sysprop APIs convert the string representation to typed C++, Java, or Rust values.
A missing property, an empty value, and a default selected by an API are different states.
Properties are lightweight global configuration/state; misuse creates hidden coupling, compatibility failures, and security vulnerabilities.
Read path and write path
Build / boot inputs
partition build.prop + boot parameters + init rules
│
▼
init property service
│ validate + SELinux
┌───────┴────────┐
▼ ▼
property memory persistent storage
mapped by readers for persist.* values
│
▼
libc / libbase / generated Sysprop getters
Writer process ──socket request──> init property serviceThe optimization is deliberately one-sided
Frequent reads avoid conventional IPC by consulting mapped property data.
Applications still need permission/visibility to read protected property contexts.
Writes are serialized through the property service so policy and mutation rules cannot be bypassed.
Property change notifications/triggers occur only after an accepted update.
Persistent properties require storage availability and special handling during boot.
The old Android source story no longer fits
Modern AOSP uses C++ init/property-service code, not the historical
init.cand hard-coded path list shown in early Android releases.Treble split platform and vendor partitions and made cross-partition property stability a compatibility concern.
Property files are generated/loaded per partition rather than treating
/system/build.propas the whole source of truth.SELinux property contexts and neverallow rules govern who can read and write.
Android 10+ provides
.syspropschemas and generated APIs for stable cross-boundary access.
1. Inspect properties from an authorized device
adb shell getprop ro.build.version.release
adb shell getprop ro.product.cpu.abilist
adb shell getprop | head -n 20The first commands print selected read-only build/device values. The final command previews property names and values visible to the shell domain.getprop is observation, not a public application API
adb shellexecutes under the device shell security domain, not with arbitrary app privileges.ro.*conventionally identifies values set once during boot; do not assume every prefix alone defines policy.Property availability and names vary by Android release, product, partition, and vendor implementation.
A property visible to shell may be hidden from untrusted apps or unsupported as a compatibility contract.
Property dumps can reveal build, radio, hardware, debugging, or operational data; sanitize logs before sharing.
2. Do not experiment with setprop on production devices
adb shell setprop debug.example.feature.enabled true
adb shell getprop debug.example.feature.enabledIf policy permits the shell domain to create/write this property, the read returns true. Production builds commonly reject undeclared or unauthorized writes.Risk level: caution. Review the command before running it.
A successful shell write proves very little
setproprequests a runtime mutation; the property service makes the final decision.Userdebug/eng policy and root access differ materially from production user builds.
An undeclared property may fail due to property-context mapping or SELinux denial.
debug.*is not persistent across reboot by default and must never carry secrets.Never change security, radio, encryption, boot, or vendor properties on a user device merely to test an article.
Property naming communicates ownership and lifecycle
AOSP recommends structured snake-case names such as
[{prefix}.]{group}[.{subgroup}]*.{name}[.{type}].Omit a prefix for ordinary runtime properties, use
rofor set-once data, orpersistonly when reboot persistence is justified.Suffixes such as
.enabled,.config, or.timeoutmillisclarify type/intent.Choose established subsystem groups where possible instead of vague
sys,system, ordefaultbuckets.Property names become compatibility surface; renaming or changing meaning can break system/vendor/Mainline consumers.
Partition ownership after Treble
Platform properties belong to system-side components.
Vendor and ODM properties belong to their respective hardware/product partitions.
The process that sets a partition’s build properties runs under the corresponding init context, such as
vendor_initfor vendor/ODM inputs.A property read across system/vendor or Mainline boundaries must remain stable.
Do not use a property as an undocumented side channel between a HAL and its clients; expose hardware-owned configuration through the appropriate stable HAL API.
3. Define a property context and type
audio.demo.volume.level u:object_r:audio_demo_prop:s0 exact int
audio.demo.feature.enabled u:object_r:audio_demo_prop:s0 exact boolContext mapping is both validation and security plumbing
The exact property name maps to the SELinux type
audio_demo_prop.exactprevents the entry from unintentionally matching a namespace prefix.intandboolmake the property service reject malformed writes.Define the property type with the narrow platform/vendor accessibility macro appropriate to ownership.
Grant only required read/write rules and preserve neverallow constraints; never weaken SELinux globally to make a set succeed.
Exact and prefix mappings
audio.demo.status u:object_r:audio_demo_status_prop:s0 exact enum on off unknown
audio.demo.debug. u:object_r:audio_demo_debug_prop:s0 prefix stringExact beats prefix when both match
The status property accepts only the declared enum values.
The prefix entry covers properties beginning with
audio.demo.debug..Broad prefixes expand the number of names sharing policy; use them only for a coherent controlled family.
When an exact and prefix rule conflict, AOSP specifies that the exact entry takes precedence.
Run property/sepolicy build checks because text order alone is not a safe policy design.
4. Set build-time values through product configuration
PRODUCT_SYSTEM_PROPERTIES += \
audio.demo.feature.enabled=true
PRODUCT_VENDOR_PROPERTIES += \
ro.audio.demo.hardware.config=codec_v2The partition variable chooses where the value is built
Product property assignments are emitted into the selected partition’s build property data.
Use
=for a definitive assignment; AOSP also supports?=defaults with defined precedence behavior.Place a property in the partition that owns it and matches its SELinux/property contract.
A read-only
ro.*value is intended to be established during boot/build flow, not changed later.Duplicate conflicting assignments should fail review/build rather than depend on accidental ordering.
5. React to properties in init scripts
on property:audio.demo.feature.enabled=true
start audio_demo
on property:audio.demo.feature.enabled=false
stop audio_demoProperty triggers translate state into init actions
The action runs when the property transition matches the trigger and during applicable boot property-trigger evaluation.
startandstopaddress a service declared in init configuration.The property writer still needs SELinux set permission and the service/action needs its own correct domain policy.
Design idempotent transitions and avoid loops where actions repeatedly rewrite their own trigger property.
Use explicit service lifecycle APIs where a property-triggered global state machine would be fragile.
Substitution and defaults inside init
on property:audio.demo.feature.enabled=true
write /sys/devices/platform/demo/level ${audio.demo.volume.level:-50}The default applies when the property is empty/unset
${property}substitutes a property value in supported init contexts.${property:-default}supplies a default when no usable value is present.Writing sysfs requires correct node ownership, lifecycle, range checking, and SELinux access.
A typed property prevents non-integer text but does not enforce a device-specific numeric range by itself.
A dedicated service/HAL is preferable for complex validation, errors, transactions, or hardware ownership.
6. Define stable properties with a Sysprop schema
owner: Platform
module: "android.sysprop.AudioDemoProperties"
prop {
api_name: "feature_enabled"
type: Boolean
prop_name: "audio.demo.feature.enabled"
scope: Public
access: ReadWrite
}
prop {
api_name: "hardware_config"
type: String
prop_name: "ro.audio.demo.hardware.config"
scope: Public
access: Readonly
}The schema replaces magic strings at call sites
ownerrecords whether Platform, Vendor, or ODM owns the contract.modulebecomes a Java class and C++ namespace; Rust bindings are generated for supported consumers.typeproduces typed optional values instead of manual string parsing.Readonlyemits a getter; writable access emits getter/setter APIs, subject to runtime property policy.ro.*properties cannot be declared read-write.Public scope and generated API stability do not grant every process SELinux permission automatically.
7. Build the generated API library
sysprop_library {
name: "AudioDemoProperties",
srcs: ["AudioDemoProperties.sysprop"],
property_owner: "Platform",
}Soong generates language-specific clients
The source schema is compiled into Java, C++, and Rust-facing library variants as supported.
property_ownermust agree with the schema and partition design.Consumers depend on the generated library rather than importing a private header or reflecting on hidden Java APIs.
Build-time API checks detect incompatible stable-sysprop changes.
Generated functions still use the underlying property mechanism and enforce its runtime access policy.
8. Read through generated C++ APIs
#include <AudioDemoProperties.sysprop.h>
using android::sysprop::AudioDemoProperties;
bool enabled = AudioDemoProperties::feature_enabled().value_or(false);
std::string config =
AudioDemoProperties::hardware_config().value_or("unknown");Optional values force a missing-value decision
The generated getter returns an optional typed value.
value_or(false)andvalue_or("unknown")define explicit local defaults.No property-name string is duplicated in the consumer.
The header/library name is generated by the sysprop build and must be linked in the Soong module.
Defaults should be safe and compatible—not silently mask a required factory configuration.
Low-level native APIs are fallback tools
#include <android-base/properties.h>
const bool enabled =
android::base::GetBoolProperty("audio.demo.feature.enabled", false);
const std::string config = android::base::GetProperty(
"ro.audio.demo.hardware.config", "unknown");libbase is safer than raw buffers, but still stringly named
GetBoolPropertyparses supported boolean encodings and applies an explicit default.GetPropertyreturns a string/default without manually allocating aPROP_VALUE_MAXbuffer.AOSP recommends generated Sysprop APIs where possible, especially across stable boundaries.
Host binaries can use appropriate libbase property wrappers in supported build contexts.
Never let untrusted input select arbitrary property names or treat properties as secret storage.
Java and ordinary applications
Platform Java code can consume generated Sysprop classes when its build dependency and API scope permit.
android.os.SystemPropertiesexists for platform/internal Java code but is not an ordinary public Android SDK API.Third-party apps must not depend on reflection into hidden property APIs.
Use Build fields and public manager APIs for supported device/platform facts.
Use app-owned storage/configuration for app state, respecting backup, multi-user, direct-boot, privacy, and lifecycle semantics.
Persistent properties are special
persist.*indicates that accepted values should survive reboot.Persistent data loads only after the relevant data storage is available.
A write can cause durable storage I/O; do not use persistent properties as a high-frequency telemetry channel.
Define ownership, default, migration, corruption/reset behavior, privacy, and factory-reset expectations.
Persist only when reboot survival is necessary; prefer the owning service’s structured storage for complex state.
Property value/name constraints
Use generated or libbase APIs instead of hard-coding Bionic buffer limits.
Bionic retains
PROP_VALUE_MAX; historical and current behavior around long read-only values differs, so validate against the target branch/API.Property names and values are not arbitrary databases or blobs.
Large, structured, frequently changing, transactional, or per-user data belongs elsewhere.
Types in
property_contextsvalidate representation, not business meaning, units, dependencies, or ranges.
Watch a property during debugging
adb shell getprop -T audio.demo.feature.enabled
adb shell getprop audio.demo.feature.enabled
adb shell dmesg | grep -i "avc:.*property"Where supported, `getprop -T` prints the declared type; the value query prints current state. Kernel logs may show SELinux denials only when the device/build permits access.A denial is evidence, not a request to disable SELinux
Command options vary by Android/toybox release; check
adb shell getprop --helpon the target.Production devices may restrict dmesg, and audit records may appear through other authorized logging paths.
Capture source/target context, property type, operation, build fingerprint, and policy version.
Fix property ownership/context and the narrow allow rule; never switch SELinux permissive in a production solution.
Do not publish full property or audit dumps without removing device/user/vendor-sensitive values.
When a property write fails
Permission denied / SELinux denial: caller lacks set permission for the mapped property type.
Invalid argument/value: name, length, declared bool/int/enum syntax, or other validation failed.
Read-only property: an
ro.*/write-once value was already established or schema disallows setters.Property has no context: the name is undeclared/mapped to an unsuitable default and modern policy rejects it.
Works on userdebug, fails on user: engineering-only policy/root masked a production access violation.
Value changes but service does not react: trigger spelling/value, init import, boot ordering, or service definition is wrong.
Vendor can read but upgrade breaks: cross-partition magic-string contract was not defined/stabilized as a Sysprop API.
Persisted value disappears: storage/boot load, property namespace, policy, factory reset, or write timing is wrong.
Choose another configuration mechanism when appropriate
Use a stable AIDL HAL method when hardware is the source of truth.
Use system config XML for static structured configuration with a schema.
Use resource overlays for product-specific framework/resource values.
Use DeviceConfig or the appropriate platform configuration service for supported dynamically managed flags.
Use files/databases owned by a service for structured, transactional, high-volume, sensitive, or per-user state.
Use Binder/AIDL callbacks when clients need authenticated operations, errors, subscriptions, and lifecycle semantics.
Production design checklist
The property is genuinely the right mechanism and has one documented owner.
Name, prefix/lifecycle, partition, type, default, units, range, read/write actors, and persistence are specified.
SELinux property type, exact/prefix context, allow and neverallow policy use least privilege.
Cross-partition/Mainline access is defined through a stable generated Sysprop API.
Build-time assignments land in the owning partition and runtime setters are authorized.
Init triggers are idempotent, loop-free, and tested across boot/order/restart/factory-reset paths.
User and userdebug builds, OTA compatibility, missing/invalid values, and security/privacy logging are tested.
No third-party app, HAL side channel, secret, per-user state, or high-frequency data abuses the global property store.
Primary references
AOSP: Add system properties defines naming, ownership, SELinux contexts, build-time values, init access, generated APIs, and low-level alternatives.
AOSP: Implement system properties as APIs specifies
.syspropschemas and typed C++/Java/Rust generation.AOSP configuration overview compares sysprops, HALs, XML, and resource overlays and warns about global-property misuse.
Current init property service source is the authoritative evolving implementation.
Bionic system property API documents low-level native interfaces and limits.
Comments and corrections