Zygote
Binder & IPCintermediate

AIDL: the interface language

Write the contract once, and the build generates the Parcel marshalling for both sides. AIDL is the difference between 'call transact()' and 'call getTaskCount()'.

11 minAPI 1–37 · 17interactivequizverified 2026-10-27

The Binder driver moves bytes. It has no idea what a Task is, which method you meant, or whether an argument is in or out. Every one of those decisions has to be made somewhere, and doing it by hand in both processes is how you get two implementations that disagree.

TL;DR#

  • An .aidl file declares one interface in Java-like syntax. The build generates an interface, a Stub for the server and a Proxy for the client.
  • Arguments must be marked in, out or inout unless they are primitives, String, IBinder or another AIDL interface, which default to in.
  • oneway methods have no return value and no exceptions. Calls from the same node stay ordered but do not wait.
  • Interfaces are versioned by appending: add methods at the end, never renumber, and assign explicit transaction codes when you need to.
  • The same language has three backends — Java, NDK (C++) and Rust — which is why AIDL replaced HIDL for vendor interfaces.12

Mental model#

Think of a restaurant menu and its kitchen ticket system.

The menu is the .aidl file: a list of dishes with names and descriptions. It is the only thing the customer and the kitchen both agree on.

The ticket is a transaction: a numbered order with the table, the dish code and any special requests. The waiter does not shout the order across the room; the ticket travels on a rail.

The order pad is the generated Proxy: it turns "one margherita, no basil" into a ticket with the right code. The ticket rail and the chef's numbering are the driver.

Where the analogy breaks#

A menu is prose and a kitchen can improvise. AIDL is a binary contract: the method code, the argument order and the parcel format are fixed at build time, and both sides must be built from the same definition. Change the interface without changing both sides and you do not get a creative interpretation — you get an exception, or worse, silently misread fields.

The big picture#

Loading diagram…
Text description of this diagram

A two-panel code viewer. On the left is an ITaskService.aidl file declaring getTaskCount, getTasks, addTask with an explicit transaction code, and a oneway notifyChanged. On the right is the generated Java interface: an IInterface with TRANSACTION constants, an abstract Stub extending Binder with asInterface and onTransact, and a Proxy class holding an IBinder that writes the interface token, calls transact, and reads the reply. A third view shows the hand-written Kotlin TaskService that extends ITaskService.Stub and implements the four methods.

Click the labelled steps under the diagram to trace one method from the .aidl declaration through the generated constants, the server's onTransact and the client's transact call. The generated code shown is illustrative of the documented Stub/asInterface and Proxy shape; exact output differs by AIDL version and backend.

Deep dive#

What the compiler generates#

For a single .aidl file you get a Java interface (or a Kotlin/NDK/Rust equivalent) with three layers:

  1. The interface — extends android.os.IInterface, declares your methods plus asBinder(), and defines a TRANSACTION_* integer per method.
  2. Stub — an abstract class extending Binder that implements the interface. Its onTransact reads the method code, decodes the Parcel, calls your implementation, and writes the result. It also provides asInterface(IBinder), which returns the local object when the binder is in the same process and a proxy otherwise.
  3. Proxy — a private class holding the remote IBinder. Each method writes the descriptor and arguments, calls transact(), and reads the reply.

The same-process shortcut in asInterface is easy to miss and important: a bound service in your own process does not pay for Binder at all. queryLocalInterface returns the real implementation, and the call is an ordinary virtual call.

Writing the interface#

An .aidl file holds exactly one interface or parcelable. The parameter direction markers are the part people get wrong most often:

TypeDefault directionMust be annotated?
PrimitivesinNo
String, CharSequenceinNo
IBinder, AIDL interfacesinNo
Parcelables, arrays, List, Map—Yes: in, out or inout

in means the value is marshalled from client to server. out means the server fills it and the client reads it back — which requires Binder to write the result into the reply. inout does both, and costs two copies of the data.

// ITaskService.aidl
package com.example.tasks;
 
import com.example.tasks.Task;
 
interface ITaskService {
  int getTaskCount();
 
  List<Task> getTasks();
 
  void addTask(in Task task) = 3;
 
  oneway void notifyChanged(int reason);
}

The = 3 is an explicit transaction code. By default AIDL numbers methods by their position in the file, which means inserting a method in the middle silently changes every later number. Assigning codes explicitly makes that change safe, at the cost of managing the numbers yourself.

oneway in AIDL#

Marking a method oneway has three consequences, and all three are enforced by the generated code and the driver rather than by convention:

  • The method must return void. There is no reply to carry a value.
  • It cannot throw a checked remote exception to the caller, because there is no return path.
  • The proxy sets FLAG_ONEWAY, so the driver does not block the calling thread waiting for a reply.

What oneway does not give you is parallelism. Calls from one node to one target are serialised: the second cannot run until the first has been dispatched, even though they may be handled by different threads.3 If you need true concurrency, you need multiple targets or a different design.

Versioning#

AIDL has no explicit major versions. The rules are:

  • Append methods to the end of an interface; never reorder or remove.
  • Append fields to the end of a parcelable.
  • Put an incompatible change in a new package name rather than breaking the old one.

That is in-place versioning, and it is what lets a vendor interface evolve while old clients keep working. For HALs there is an extra layer: interfaces that cross the system/vendor boundary must be frozen at a released version, and VTS checks that both sides agree on the exact frozen definition.2

Three backends, one language#

AIDL is not only a Java tool. The same .aidl file can generate:

  • Java — for app and framework code, using libbinder through JNI.
  • NDK (C++) — for native services, linking libbinder_ndk.
  • Rust — for new native code, with a binder crate.

That portability is why AIDL replaced HIDL. HIDL was designed for the vendor interface and was C++-centric; AIDL already existed for app developers and could be extended to cover the same ground with a syntax people already knew. HIDL has been deprecated since Android 10.4

Errors, death and the things AIDL does not do#

AIDL gives you RemoteException and, for service-specific failures, a status code or ServiceSpecificException. It does not give you:

  • Retries. A failed transaction is failed; retrying is your policy.
  • Timeouts. A synchronous call waits as long as the server takes.
  • Liveness. DeathRecipient tells you the process died, not that it is healthy.
  • Streaming. For large data, pass a file descriptor or a shared-memory region and keep the Binder call for control.

The practical consequence is that every AIDL interface is a small distributed system, and the failure modes you would design for in a network client — timeouts, partial failure, version skew — apply here too, just with much lower latency.

How it evolved#

Versions9 releases
APIVersionReleasedWhat changed
11.02008-09-23—
102.3.3 Gingerbread2011-02-09—
113.0 Honeycomb2011-02-22—
123.1 Honeycomb2011-05-10—
144.0 Ice Cream Sandwich2011-10-18—
30112020-09-08—
31122021-10-04—
3313 Tiramisu2022-08-15—
3717 Cinnamon Bun2026-06-16—

The trajectory is consistent: AIDL started as a tool for app services, gained parcelable support in Android 10, and then became the recommended interface language for HALs, with Android 11 adding stable AIDL for the system/vendor boundary and later releases adding native and Rust backends.2

Under the hood#

Curiosity corner#

  • AIDL is older than the HAL use. It shipped for app-to-app bound services long before it was considered for the vendor interface.
  • The interface descriptor is a string, and it is checked. It is the only runtime evidence that the two sides were built from the same .aidl file.
  • oneway calls are ordered. They are asynchronous, not unordered, which is a distinction that has caused more than one race condition.

Try it#

Compile an AIDL file by hand
aidl --lang=java -I src -o out src/com/example/tasks/ITaskService.aidl

The aidl binary ships with the Android build tools; the exact output filename depends on the backend.

Find AIDL interfaces on a device
adb shell service list | grep -i hardware | head

Example names; the set of hardware services depends on the device.

Build it#

The builder track's AIDL calculator project walks through writing the .aidl file, implementing the Stub, binding from a client, and observing the generated code. It is the fastest way to make the sequence above concrete. See I01 — AIDL calculator.

Check yourself#

Check yourself

1 / 12

Why does a Binder transaction copy the payload only once?

0 answered · 0 correct

Sources#

Sources

Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.

  1. docsAndroid Interface Definition Language (AIDL) | Background workdeveloper.android.comaccessed 2026-10-27↩
  2. docsAIDL for HALssource.android.comaccessed 2026-10-27↩
  3. docsBinder threadingsource.android.comaccessed 2026-10-27↩
  4. docsHIDLsource.android.comaccessed 2026-10-27↩
  5. docsBinder overviewsource.android.comaccessed 2026-10-27↩