Skip to content

How to Load Custom JARs into Android SYSTEMSERVERCLASSPATH

In a platform engineering community I participate in, a developer recently asked: “How can I load custom JARs located in the system_ext partition into the SYSTEMSERVERCLASSPATH so they are available to the system_server process at runtime?”

This is a common hurdle when customizing AOSP (Android Open Source Project). Unlike standard app development where you simply bundle a library, modifying the system_server requires interacting with the build system (Soong/Make) and the Android runtime’s strict partition rules.

To include a JAR from the system_ext partition in the SYSTEMSERVERCLASSPATH:

  1. Update your Product Makefiles: Add your library to the PRODUCT_SYSTEM_EXT_SYSTEM_SERVER_JARS variable in your device.mk or similar build file.
  2. Define the Module: Ensure your Android.bp defines the java_library with installable: true and targets the system_ext partition.
  3. Define Permissions: Create an XML file in /system_ext/etc/permissions/ to “allow-list” the library.
  4. Rebuild: The build system will automatically update the systemserverclasspath.pb (binary protobuf) used by the system during boot.

Since Android 12, classpaths are increasingly managed via specialized build variables to ensure partition safety. Here are the two primary ways to achieve this.

Section titled “Method 1: Using PRODUCT_SYSTEM_EXT_SYSTEM_SERVER_JARS (Recommended)”

This is the standard AOSP way to ensure your library is included in the classpath specifically for the system_server process.

1. Define your library in Android.bp (Android 12, 13, 14 — illustrative example)

java_library {
name: "com.example.systemserver.ext",
srcs: ["src/**/*.java"],
installable: true,
system_ext_specific: true, // This places it in the system_ext partition
sdk_version: "core_platform",
}

2. Update the product configuration In your device/vendor/model/device.mk file:

# Android 12 and above
PRODUCT_SYSTEM_EXT_SYSTEM_SERVER_JARS += com.example.systemserver.ext

Why this works: The build system collects all libraries listed in this variable and generates a system configuration that tells the Zygote process to include these entries when forking the system_server.

Method 2: Handling Hidden API and Permissions

Section titled “Method 2: Handling Hidden API and Permissions”

Simply adding the JAR to the classpath is often not enough. If your JAR needs to be accessed by other system components or if you are using it as a shared library, you must declare it.

Create a permission XML file: Place this file in device/vendor/model/permissions/my_lib.xml.

<?xml version="1.0" encoding="utf-8"?>
<!-- Android 11+ illustrative example -->
<permissions>
<library
name="com.example.systemserver.ext"
file="/system_ext/framework/com.example.systemserver.ext.jar"
/>
</permissions>

Add to Makefile:

PRODUCT_COPY_FILES += \
device/vendor/model/permissions/my_lib.xml:$(TARGET_COPY_OUT_SYSTEM_EXT)/etc/permissions/my_lib.xml

1. The Boot Loop (The “ClassNotFound” Error)

Section titled “1. The Boot Loop (The “ClassNotFound” Error)”

If you add a JAR to SYSTEMSERVERCLASSPATH but the file is missing from the physical partition, the device will boot loop.

  • Fix: Check your build output. Verify the JAR exists at out/target/product/[name]/system_ext/framework/your_lib.jar.

The system_server classpath is highly optimized. If you add a JAR manually via adb push to a running device, it will not work because the binary protobuf (/system/etc/classpaths/systemserverclasspath.pb) is immutable at runtime.

  • Action: You must perform a full build or at least a build that regenerates the system image to update the classpath protobuf.

Even if the library is in the classpath, system_server might be blocked from reading it due to SELinux contexts.

  • Diagnostic: Run adb shell dmesg | grep avc or logcat | grep denied.
  • Fix: Ensure your file has the system_file or system_ext_file context. Usually, the build system handles this automatically if you use system_ext_specific: true.

Can I add JARs to the SYSTEMSERVERCLASSPATH at runtime? No. The SYSTEMSERVERCLASSPATH is defined during the build and locked when the system_server starts. If you need to load code dynamically, you must use a PathClassLoader or DexClassLoader within your Java code, but this is generally discouraged for core system services due to security and performance overhead.

What is the difference between PRODUCT_BOOT_JARS and PRODUCT_SYSTEM_SERVER_JARS? PRODUCT_BOOT_JARS are loaded by the Zygote and are available to every single Android process (apps, system services, etc.). PRODUCT_SYSTEM_SERVER_JARS are specifically appended only to the classpath of the system_server process. You should use the latter for custom system services to keep the global boot classpath lean.

Does order matter in the classpath? Yes. If you have duplicate class names, the first one found in the classpath string takes precedence. The build system usually appends system_ext jars after the main system jars but before vendor jars. Check the generated .pb file using the out/host/linux-x86/bin/classpaths_proto_content_printer tool to verify the final order.

  • Module defined in Android.bp with system_ext_specific: true.
  • Module name added to PRODUCT_SYSTEM_EXT_SYSTEM_SERVER_JARS.
  • XML permission file defined and copied to /system_ext/etc/permissions/.
  • No SELinux violations found in dmesg.
  • Verified the JAR is physically present in the framework directory of the target partition.