AIDL Backends

An AIDL backend is a target for stub code generation. When using AIDL files, you always use them in a particular language with a specific runtime. Depending on the context, you should use different AIDL backends.

AIDL has the following backends:

Backend Language API surface Build systems
Java Java SDK/SystemApi (stable*) all
NDK C++ libbinder_ndk (stable*) aidl_interface
CPP C++ libbinder (unstable) all
  • These API surfaces are stable, but many of the APIs, such as those for service management, are reserved for internal platform use and are not available to apps. For more information on how to use AIDL in apps, see developer documentation.

The NDK backend is new in Android 10.

Build systems

Depending on the backend, there are two different ways to compile AIDL into stub code. More detailed documentation on the build systems can be seen in the Soong Module Reference.

Core build system

In any cc_ or java_ Android.bp module (or in their equivalents), .aidl files can be specified as source files. In this case, the Java/CPP backends of AIDL are used (not the NDK backend), and the classes to use the corresponding AIDL files are added automatically to the module. Options (such as local_include_dirs, which tells the build system the root path to AIDL files in that module) can be specified in these modules under an aidl: group. For more details, see specific documentation on the build system in the Soong Module Reference.


See Stable AIDL. Types used with this build system must be structured, that is, expressed in AIDL directly. This means that custom parcelables cannot be used.


The aidl compiler can be considered a reference implementation for types. When creating an interface, you can see the resulting interface file by invoking aidl --lang=<backend> .... When using the aidl_interface module, the output can also be seen in out/soong/.intermediates/<path to module>/.

Java/AIDL Type C++ Type NDK Type
boolean bool bool
byte int8_t int8_t
char char16_t char16_t
int int32_t int32_t
long int64_t int64_t
float float float
double double double
String android::String16 std::string
android.os.Parcelable android::Parcelable N/A
IBinder android::IBinder ndk::SpAIBinder
T[] std::vector<T> std::vector<T>
byte[] std::vector<uint8_t> std::vector<int8_t>1
List<T> std::vector<T>2 N/A
FileDescriptor android::base::unique_fd N/A
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor
interface type (T) android::sp<T> std::shared_ptr<T>
parcelable type (T) T T
union type (T)3 T T

1. In Android 12 or higher, byte arrays use uint8_t instead of int8_t for compatibility reasons.

2. The C++ backend only supports List<String> and List<IBinder>. In general, it is recommended to use array types like T[], since they work in all backends.

3. Union types are supported in Android 12 or higher.

Directionality (in/out/inout)

When you're specifying the types of the arguments to functions, you can specify them as in, out, or inout. This controls which direction information is passed for an IPC call. in is the default direction, and it indicates data is passed from the caller to the callee. out means that data is passed from the callee to the caller. inout is the combination of both of these. However, the Android team recommends that you avoid using the argument specifier inout. If you use inout with a versioned interface and an older callee, the additional fields that are present only in the caller get reset to their default values.


The C++ backend lets you to choose whether strings are utf-8 or utf-16. Declare strings as @utf8InCpp String in AIDL to automatically convert them to utf-8. The NDK backend always uses utf-8 strings. For more information about the utf8InCpp annotation, see Annotations in AIDL.


For types which can be null in Java, they can be annotated with @nullable to expose null values to C++/NDK. By default native servers reject null values. The only exceptions to this are interface and IBinder types which can always be null. For more information about the nullable annotation, see Annotations in AIDL.

Custom Parcelables

In the C++ and Java backends in the core build system, you can declare a parcelable that is implemented manually in a target backend (in C++ or in Java).

    package my.package;
    parcelable Foo;

or with C++ header declaration:

    package my.package;
    parcelable Foo cpp_header "my/package/Foo.h";

Then, you can use this parcelable as a type in AIDL files, but it will not be generated by AIDL.

Default values

Structured parcelables can declare per-field default values for primitives, Strings, and arrays of these types.

    parcelable Foo {
      int numField = 42;
      String stringField = "string value";
      char charValue = 'a';

In the Java backend, when default values are missing, fields are initialized as zero values for primitive types and null for non-primitive types.

In other backends, fields are initialized with default initialized values when default values are not defined. For example, in the C++ backend, String fields are initialized as an empty string and List<T> fields are initialized as an empty vector<T>. @nullable fields are initialized as null value.


In Java, exceptions are used to propagate errors between services, such as android.os.RemoteException. However, native code in Android doesn't use exceptions, so the CPP backend uses android::Status, and the NDK backend uses ndk::ScopedAStatus to represent the exceptions that a method can return. Every method generated by AIDL returns one of these, representing the status of the method which is returned.

Using various backends

These instructions are specific to Android platform code. These examples use a defined type, my.package.IFoo.

Importing types

Whether the defined type is an interface, parcelable, or union, you can import it in Java:

    import my.package.IFoo;

Or in the CPP backend:

    #include <my/package/IFoo.h>

Or in the NDK backend (notice the extra aidl namespace):

    #include <aidl/my/package/IFoo.h>

Implementing services

To implement a service, you must inherit from the native stub class. This class reads commands from the binder driver and executes the methods that you implement. Imagine that you have an AIDL file like this:

    package my.package;
    interface IFoo {
        int doFoo();

In Java, you must extend from this class:

    import my.package.IFoo;
    public class MyFoo extends IFoo.Stub {
        int doFoo() { ... }

In the CPP backend:

    #include <my/package/BnFoo.h>
    class MyFoo : public my::package::BnFoo {
        android::binder::Status doFoo(int32_t* out) override;

In the NDK backend (notice the extra aidl namespace):

    #include <aidl/my/package/BnFoo.h>
    class MyFoo : public aidl::my::package::BnFoo {
        ndk::ScopedAStatus doFoo(int32_t* out) override;

Registering and getting services

Services in platform Android are usually registered with the servicemanager process. In addition to the APIs below, there are some APIs which check the service (meaning they return immediately if the service isn't available). Check the corresponding servicemanager interface for exact details. These operations can only be done when compiling against platform Android.

In Java:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // getting
    myService = IFoo.Stub.asInterface(ServiceManager.getService("service-name"));
    // waiting until service comes up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));

In the CPP backend:

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // getting
    status_t err = getService<IFoo>(String16("service-name"), &myService);
    // waiting until service comes up (new in Android 11)
    myService = waitForService<IFoo>(String16("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = waitForDeclaredService<IFoo>(String16("service-name"));

In the NDK backend (notice the extra aidl namespace):

    #include <android/binder_manager.h>
    // registering
    status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // getting
    myService = IFoo::fromBinder(SpAIBinder(AServiceManager_getService("service-name")));
    // is a service declared in the VINTF manifest
    // VINTF services have the type in the interface instance name.
    bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
    // wait until a service is available (if isDeclared or you know it's available)
    myService = IFoo::fromBinder(SpAIBinder(AServiceManager_waitForService("service-name")));

You can request to get a notification for when a service hosting a binder dies. This can help to avoid leaking callback proxies or assist in error recovery. Make these calls on binder proxy objects.

In Java, use android.os.IBinder::linkToDeath. In the CPP backend, use android::IBinder::linkToDeath. In the NDK backend, use AIBinder_linkToDeath.

Bug reports and debugging API for services

When bugreports run (for example, with adb bugreport), they collect information from all around the system to aid with debugging various issues. For AIDL services, bugreports use the binary dumpsys on all services registered with the service manager to dump their information into the bugreport. You can also use dumpsys on the commandline to get information from a service with dumpsys SERVICE [ARGS]. In the C++ and Java backends, you can control the order in which services get dumped by using additional arguments to addService. You can also use dumpsys --pid SERVICE to get the PID of a service while debugging.

To add custom output to your service, you can override the dump method in your server object like you are implementing any other IPC method defined in an AIDL file. When doing this, you should restrict dumping to the app permission android.permission.DUMP or restrict dumping to specific UIDs.

In the Java backend:

    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
        @Nullable String[] args) {...}

In the C++ backend:

    status_t dump(int, const android::android::Vector<android::String16>&) override;

In the NDK backend:

    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;

Dynamically getting interface descriptor

The interface descriptor identifies the type of an interface. This is useful when debugging or when you have an unknown binder.

In Java, you can get the interface descriptor with code such as:

    service = /* get ahold of service object */
    ... = service.asBinder().getInterfaceDescriptor();

In the CPP backend:

    service = /* get ahold of service object */
    ... = IInterface::asBinder(service)->getInterfaceDescriptor();

The NDK backend doesn't support this functionality.

Statically getting interface descriptor

Sometimes (such as when registering @VintfStability services), you need to know what the interface descriptor is statically. In Java, you can get the descriptor by adding code such as:

    import my.package.IFoo;

In the CPP backend:

    #include <my/package/BnFoo.h>
    ... my::package::BnFoo::descriptor

In the NDK backend (notice the extra aidl namespace):

    #include <aidl/my/package/BnFoo.h>
    ... aidl::my::package::BnFoo::descriptor

Enum Range

In native backends, you can iterate over the possible values an enum can take on. Due to code size considerations, this isn't currently supported in Java.

For an enum MyEnum defined in AIDL, iteration is provided in the CPP backend with ::android::enum_range<MyEnum>() and in the NDK backend with ::ndk::enum_range<MyEnum>().

Thread management

Every instance of libbinder in a process maintains one threadpool. For most usecases, this should be exactly one threadpool, shared across all backends. The only exception to this is when vendor code might load another copy of libbinder to talk to /dev/vndbinder. Since this is on a separate binder node, the threadpool isn't shared.

For the Java backend, the threadpool can only increase in size (since it is already started):

    BinderInternal.setMaxThreads(<new larger value>);

For the C++ backend, the following operations are available:

    // set max threadpool count (default is 15)
    status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(numThreads);
    // create threadpool
    // add current thread to threadpool (adds thread to max thread count)

Similarly, in the NDK backend:

    bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);