microg/GmsCore · error · IOException
Failed to create directories: ${parentDir.absolutePath}
Error message
Failed to create directories: ${parentDir.absolutePath} What it means
HttpClient.download throws this IOException when the target file's parent directory does not exist and mkdirs() fails to create it. The library refuses to continue the download because writing the file would fail anyway, and includes the offending directory path in the message.
Source
Thrown at vending-app/src/main/java/org/microg/vending/billing/core/HttpClient.kt:58
private val client = singleInstanceOf { HttpClient(OkHttp) {
expectSuccess = true
install(HttpTimeout)
} }
private val clientWithCache = singleInstanceOf { HttpClient(OkHttp) {
expectSuccess = true
install(HttpCache)
install(HttpTimeout)
} }
suspend fun download(
url: String,
downloadFile: File,
params: Map<String, String> = emptyMap()
): File = downloadFile.also { toFile ->
val parentDir = downloadFile.getParentFile()
if (parentDir != null && !parentDir.exists() && !parentDir.mkdirs()) {
throw IOException("Failed to create directories: ${parentDir.absolutePath}")
}
FileOutputStream(toFile).use { download(url, it, params) }
}
suspend fun download(
url: String,
downloadTo: OutputStream,
params: Map<String, String> = emptyMap(),
downloadedBytes: Long = 0,
emitProgress: (bytesDownloaded: Long) -> Unit = {}
) {
try {
Log.d(TAG, "download downloadedBytes:$downloadedBytes")
client.prepareGet(url.asUrl(params)){
if (downloadedBytes > 0) {
headers {
append(HttpHeaders.Range, "bytes=$downloadedBytes-")View on GitHub (pinned to 157c9d86ac)
Solutions
- Request MANAGE_EXTERNAL_STORAGE / WRITE_EXTERNAL_STORAGE or use app-scoped storage (getExternalFilesDir) instead
- Create the parent directory yourself beforehand with parentDir.mkdirs() and check the result
- Verify the path exists, is writable, and the filesystem is not read-only or full
- Download to internal cache dir first and copy afterwards if the destination is restricted
Example fix
// before
httpClient.download(url, File("/sdcard/downloads/file.bin"))
// after
val dest = File(context.getExternalFilesDir(null), "file.bin")
dest.parentFile?.mkdirs()
httpClient.download(url, dest) Defensive patterns
Strategy: validation
Validate before calling
val parent = destFile.parentFile
if (parent != null && !parent.exists() && !parent.mkdirs()) {
throw IOException("Cannot create ${parent}")
} Type guard
fun File.isWritableTarget(): Boolean {
val p = parentFile ?: return false
return (p.exists() || p.mkdirs()) && p.canWrite()
} Try / catch
try {
httpClient.download(url, destFile)
} catch (e: IOException) {
if (e.message?.startsWith("Failed to create directories") == true) {
// fall back to app-scoped storage
}
} Prevention
- Use context.getExternalFilesDir(null) for download targets on Android
- Request storage permissions at runtime before downloading
- Check canWrite() on the parent directory before starting the download
When it happens
Trigger: Calling download(url, File("/some/path/file.bin")) where /some/path doesn't exist and cannot be created — e.g. missing WRITE_EXTERNAL/storage permission, read-only filesystem, path on unmounted storage, or invalid path characters.
Common situations: Downloading to external storage without runtime storage permission on Android 6+; using a path on scoped-storage-restricted locations (Android 10+); SD card removed or emulated storage full.
Understand the failure class
Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.
Related errors
- Could not read from parcel file descriptor
- Access denied, missing google package permission or GET_ACCO
- Failed to read license or metadata
- Initialization failed
- app op denied
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/155e813a48c8dd88.
Report an issue: GitHub.