microg/GmsCore · error · RuntimeException
does not contain res/raw/third_party_licenses
Error message
does not contain res/raw/third_party_licenses
What it means
getLicenseText throws this RuntimeException when the JAR file referenced by the License's path does not contain a JarEntry named "res/raw/third_party_licenses". The library expects license text for non-empty license paths to live inside an APK/JAR at that exact resource path (as produced by the OSS licenses plugin). If the entry is missing, the offset/length stored in the metadata cannot be resolved, so the library aborts.
Source
Thrown at play-services-oss-licenses/src/main/java/org/microg/gms/oss/licenses/LicenseUtil.java:71
}
public static String getLicenseText(Context context, License license) {
if (license.getPath().isEmpty()) {
Resources resources = context.getApplicationContext().getResources();
InputStream is = resources.openRawResource(resources.getIdentifier("third_party_licenses", "raw", context.getPackageName()));
try {
if (is.skip(license.getOffset()) != license.getOffset()) {
throw new RuntimeException("Failed to read license");
}
} catch (IOException e) {
throw new RuntimeException("Failed to read license", e);
}
return readStringAndClose(is, license.getLength());
} else {
try (JarFile jar = new JarFile(license.getPath())) {
JarEntry entry = jar.getJarEntry("res/raw/third_party_licenses");
if (entry == null) {
throw new RuntimeException(license.getPath() + " does not contain res/raw/third_party_licenses");
} else {
InputStream is = jar.getInputStream(entry);
if (is.skip(license.getOffset()) != license.getOffset()) {
throw new RuntimeException("Failed to read license");
}
return readStringAndClose(is, license.getLength());
}
} catch (IOException e) {
throw new RuntimeException("Failed to read license", e);
}
}
}
private static String readStringAndClose(InputStream is, int bytesToRead) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
byte[] bytes = new byte[1024];
int read;View on GitHub (pinned to 157c9d86ac)
Solutions
- Verify the JAR at license.getPath() actually contains res/raw/third_party_licenses (e.g. unzip -l app.apk | grep third_party_licenses) and rebuild it with the OSS licenses plugin if missing
- Ensure resource shrinking/minification (e.g. shrinkResources, keep rules) is not stripping res/raw/third_party_licenses from the artifact
- Confirm the License instance came from getLicensesFromMetadata of the same app/artifact it is read from; regenerate metadata and licenses together
- If licenses live in Android resources rather than a JAR, use a License with an empty path so getLicenseText reads via Resources.openRawResource instead
Example fix
// before License license = new License(title, offset, length, "/data/app/stripped.apk"); String text = LicenseUtil.getLicenseText(context, license); // after // rebuild the artifact with the OSS licenses plugin so res/raw/third_party_licenses exists, // or load from app resources with an empty path: License license = new License(title, offset, length, ""); // empty path -> raw resource branch String text = LicenseUtil.getLicenseText(context, license);
Defensive patterns
Strategy: validation
Validate before calling
static boolean jarHasLicenses(String path) {
try (JarFile jar = new JarFile(path)) {
return jar.getJarEntry("res/raw/third_party_licenses") != null;
} catch (IOException e) { return false; }
}
// call jarHasLicenses(license.getPath()) before getLicenseText when path is non-empty Try / catch
try {
text = LicenseUtil.getLicenseText(context, license);
} catch (RuntimeException e) {
Log.w("Licenses", "Missing res/raw/third_party_licenses in " + license.getPath(), e);
text = null; // fallback path
} Prevention
- Keep the OSS licenses Gradle plugin enabled so res/raw/third_party_licenses is always packaged
- Check resource shrinker output (apk analyzer) after release builds to confirm the raw resource survives
- Only pass License objects produced by getLicensesFromMetadata of the same artifact
When it happens
Trigger: Calling LicenseUtil.getLicenseText(context, license) where license.getPath() is non-empty (a JAR/APK path) but the JAR was built without the third_party_licenses raw resource, was repackaged/stripped by a build step, or the path points to a different artifact than the one the metadata was generated against.
Common situations: ProGuard/resource shrinkers or custom packaging removing res/raw entries; using a License object deserialized from another app version whose JAR layout differs; pointing at a debug or stub artifact that lacks the licenses resource; manually constructing License with an arbitrary file path.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- Failed to read license or metadata
- Unsupported encoding UTF8. This should always be supported.
- deleteAll was set to true but keys were also provided
- Element in keys cannot be null or empty
- deleteAll=true but keys are provided
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/506e21626ae856d8.
Report an issue: GitHub.