BuildNot yet confirmed elsewhere1 publisher3 min readPublished
Android has five storage models, and your path strings only work in one of them
A practitioner's account of building a filesystem abstraction shows where the seam has to sit: both access chains end in a stream, and almost nothing before the stream travels.
The Engineer · Build desk
What happened
- A dev.to post argues a modern Android app may touch five storage models at once: app-private storage, MediaStore, SAF, cloud-backed document providers and plain JVM file APIs.
- SAF hands the app a content:// tree URI rather than a path, and the platform expects ContentResolver and DocumentsContract instead of File.
- The author says that spread is what pushed them to build a filesystem abstraction covering both Android and the JVM.
Why it matters
- cost Every relative path a library agrees to accept has to be paid for in provider operations, and the library author owns that bill because the platform will not resolve the string for them.
- constraint String concatenation as path building is off the table, so any API that takes a path has to carry a resolver behind it or narrow its promise to opening streams.
- exposure Timing and availability assumptions behind the word "file" now rest on a provider the developer never chose and cannot identify from the URI.
- decision Teams have to pick which model is canonical for their app before writing the storage layer, because taking a directory grant is what drags the other four into the codebase.
The seam worth arguing about is the single place the two access chains agree: both of them end in a stream [5]. Before that, one has a path that becomes a `File`, and the other has a URI that becomes a `ContentResolver` or `DocumentsContract` call into a provider [4][5]. That fixes the limit of what a cross-platform storage layer can honestly promise. Handing back an `InputStream` travels [15]. Doing arithmetic on names does not [8].
The worked example in the post is worth counting. A library whose API takes `documents/example.txt` gets that for free on the JVM, where the string is a path to something on a filesystem [8]. Under a user-selected SAF tree, the same string has to be walked: the selected root, then `Documents`, then `projects`, then the file [10], and each step is a document-provider operation rather than a traversal of a mounted filesystem [9]. Three segments below the root, three provider operations [13]. The string also carries no handle to any of the intermediate documents [8], so anything resembling a working directory, or a cache of resolved parents, is something the library has to invent rather than something the platform hands over.
The indirection that makes this expensive is the same indirection the author praises. A granted document tree may be backed by a provider that is not local device storage at all, possibly remote or cloud-backed, and the application does not need to know which [7]. The post calls that one of the most powerful ideas behind SAF [7], and in the next breath warns that an abstraction which tries to hide every difference can be made worse by the hiding [12]. Those two positions are compatible only if the leak is deliberate: the layer resolves, opens and streams, and refuses to pretend it can tell you a parent path.
Which is why the count of five is a ceiling rather than a requirement. The post's own routing is narrow: application data belongs in application-private storage, supported shared media belongs in MediaStore, and SAF earns its keep when the requirement is specifically that a user picks a directory and the application then works inside it [6]. Strip that requirement and two models cover the app, not five [14]. Scoped storage is described as having made storage simpler for many applications for exactly this reason, since private directories plus the appropriate platform APIs remove the need for unrestricted access to the user's shared filesystem [11].
The cost, then, sits in an application's internal vocabulary rather than in any API surface. Code that passes `String` paths between its own modules has already decided it is running on a filesystem. This is one practitioner's account of building such a library [c2b], not a platform announcement, and the supplied text breaks off mid-sentence on the question of how much difference an abstraction should hide [12].
What to watch
- Whether the author publishes the rest of the argument the supplied text cuts off, on how much difference an abstraction should hide.
- Whether DocumentsContract gains a resolve-by-relative-path call, which would remove the per-segment provider cost that libraries currently absorb.
- Whether third-party cloud services actually ship document providers users grant trees to, since that is what makes the provider indirection load-bearing rather than theoretical.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence42
- Adoption
- Insufficient
- Hype gap+12
- Incentives55
- Confidence45
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
A modern Android application may interact with application-private storage, shared media through MediaStore, user-selected documents through the Storage Access Framework (SAF), cloud-backed document providers, and traditional JVM filesystem APIs.
ReportedSupportedSource: dev.to post by the author of an Android/JVM filesystem abstractionView cited source - [2]
The author writes that the difficult part is not learning any one of these APIs, but building software that can work with different kinds of storage without forcing the entire application to understand the differences.
- [3]
The author says that problem is what led them to build a filesystem abstraction for Android and the JVM.
- [4]
Under SAF an application can receive something like content://com.android.externalstorage.documents/tree/primary%3ADocuments, which is not a filesystem path but a URI representing a document-provider resource; it is not meaningfully resolved with File(uri.path), and the operating system expects interaction through APIs such as ContentResolver and DocumentsContract.
- [5]
The traditional chain is Path to File to InputStream/OutputStream; the SAF chain is URI to ContentResolver/DocumentsContract to Provider to InputStream/OutputStream. The final result may still be a stream, but everything before that stream is different.
- [6]
Not every Android application needs SAF: if an application only needs its own application data it should use application-private storage, if it needs supported shared media MediaStore may be the right solution, and SAF becomes interesting when the requirement is to let the user choose a directory and work with files inside it.
- [7]
With appropriate user-granted permissions an application can work with a selected document tree; that tree can be served by different document providers which need not represent the device's local storage and could represent remote or cloud-backed storage, and the application does not need to know which. The author calls this one of the most powerful ideas behind SAF.
- [8]
A library API such as FileOperation("documents/example.txt") is easy on the JVM, where the application has a path referring to something on the filesystem; if the storage root is a SAF URI the library cannot simply construct content://.../documents/example.txt, because SAF URIs are not ordinary paths.
- [9]
The library has to resolve the relative path through the document provider, and each step of that resolution represents a document-provider operation rather than a normal filesystem traversal.
- [10]
The post's example resolution chain runs: selected SAF root, then Documents, then projects, then example.txt.
- [11]
Scoped Storage was an important change to Android's storage model and for many applications makes storage much simpler, because an application can use its own private directories and appropriate platform APIs without needing unrestricted access to the user's entire shared filesystem.
- [12]
The author states the goal is not to pretend SAF and the JVM filesystem are identical, and that trying to hide every difference can make an abstraction worse; the supplied text breaks off mid-sentence at that point.
- [13]
Resolving a three-segment relative path under a selected SAF root costs three document-provider operations, where the JVM equivalent is one path-to-File construction.
- [14]
An application without the requirement to let a user pick an arbitrary directory needs two of the five listed storage models, application-private storage and MediaStore, rather than all five.
- [15]
The only element shared by both access chains is the opened stream, so a portable storage API can promise stream access but cannot promise path composition.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toAndroid Storage in 2026: Scoped Storage, SAF, and Why Filesystem Abstractions Still Matter
1 article · August 23, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.