# x-file-storage and how a list of thirty providers becomes a list of six

> This Java library puts one upload call in front of local disk, FTP, SFTP, WebDAV and roughly thirty object storage providers, from Aliyun and Huawei to Cloudflare R2 and IBM COS. The design decision that makes the list tractable is not the list itself, it is that WebDAV into another tool reaches the consumer cloud drives nobody ships an SDK for.

**dromara/x-file-storage** — 一行代码将文件存储到 本地、FTP、SFTP、WebDAV、谷歌云存储、阿里云OSS、华为云OBS、七牛云Kodo、腾讯云COS、百度云 BOS、又拍云USS、MinIO、 AWS S3、FastDFS、 Azure Blob Storage、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动云 EOS、沃云 OSS、 网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 协议的平台。后续即将支持 Samba、NFS

- Repository: https://github.com/dromara/x-file-storage
- Website: https://x-file-storage.xuyanwu.cn/
- Stars: 2,222 · Forks: 328
- Language: Java
- License: Apache-2.0
- Published: 2026-09-30 · Updated: 2026-09-30 · Language: en
- Canonical page: https://hysenlabs.com/projects/dromara-x-file-storage

## Thirty providers, and why the interesting part is not the list

The pitch is one sentence: store a file with one line of code, to local disk, FTP, SFTP, WebDAV, or any of a very long list of storage platforms.

The list is worth reading once for what it reveals. The Chinese providers dominate, and not marginally: Aliyun OSS, Huawei Cloud OBS, Qiniu Kodo, Tencent COS, Baidu BOS, Upyun USS, Volcano Engine TOS, Kingsoft KS3, Meituan MSS, JD Cloud OSS, Tianyi Cloud OOS, Mobile Cloud EOS, Wo Cloud OSS, NetEase NOS, UCloud US3, QingCloud QingStor, Ping An Cloud OBS and Shou Cloud OSS. That is eighteen domestic providers, which tells you the primary user is a developer in China who has been asked to support whichever cloud their employer signed a contract with, and who has discovered that each of those has its own SDK, its own credential shape and its own idea of what a path is.

The international names in the same list are the familiar object storage set: Amazon S3 and a separate entry for S3 V2, Google Cloud Storage, Azure Blob Storage, Cloudflare R2, IBM COS, and MinIO. Plus two non-object stores, FastDFS and its Go implementation go-fastdfs, and MongoDB GridFS, which is listed twice in the same sentence of the README.

And then the last entry, which is the one that makes the whole design tractable: any other platform compatible with the S3 protocol. That clause is doing more work than anything else in the list. S3 has been the industry's interoperability baseline for a decade, and a service that supports it can be added without a new SDK, without a new dependency and without a new module. The library is, in that sense, thirty-one integrations and one fallback.

So the honest reading is that the provider count is a symptom and the protocol count is the cause. There are four standard protocols and a handful of vendor SDKs, and the vendor SDKs exist because those vendors existed before the protocols did.

The two features that are not about upload at all are the more interesting ones for an adopter. One is file migration between storage platforms, so you can move an existing bucket to another provider through the library rather than by running a migration tool. The other is metadata support, which arrived with the 2.0 rewrite, and which is what makes the library usable for anything that is not a photo album: an uploaded file can carry an object identifier, an object type and arbitrary attributes so a database row can point at it.

## WebDAV into another tool, which is how a cloud drive becomes a file path

One feature in the README deserves more attention than it gets, and it is the one that makes this library useful for people who are not enterprises.

Connect to Alist over WebDAV, and you can then use Baidu Netdisk, Tianyi Cloud Drive, Aliyun Drive, Xunlei Netdisk and other consumer storage services through this library. The README links to Alist's own documentation listing everything Alist supports.

Alist is a separate project that exposes many storage services, including consumer ones, over a set of standard protocols. WebDAV is one of them. So this library does not need an integration for Baidu Netdisk, because Alist provides a WebDAV endpoint for Baidu Netdisk and the library already speaks WebDAV. The composition is done at the configuration layer: point a WebDAV platform entry at an Alist instance and the consumer drive appears as a storage platform.

That is a much better answer than thirty more SDK integrations, and it generalises. Any service with a WebDAV endpoint becomes available to any application using this library without a line of Java. The set of reachable storage is then the union of what the library integrates directly and what an operator can bolt on in front of it, and the second set is unbounded.

It also reframes what the WebDAV entry in the provider list is for. On its own it is a file transfer protocol support, which is unremarkable. Combined with the Alist trick it is an extension point, and it is the one that matters for an individual developer whose photos are in a consumer drive with no SDK.

There is a cost to the composition, and it is the usual one: your availability now depends on a component somebody else runs. If the Alist instance is down, the platform is unavailable, and the library has no way to know or to tell you why beyond a failed call. That is a reasonable trade for a self-hosted bridge, and it is worth naming before you put a consumer drive behind a production upload path.

## of(file).upload(), and what the builder in front of it is for

The API is a fluent builder on a service you inject, and the shortest useful call is one line. The dependency and the annotation come first, and then the call.

The Spring integration is a Maven dependency alongside whichever platform SDK you are actually using:

```xml
<dependency>
    <groupId>org.dromara.x-file-storage</groupId>
    <artifactId>x-file-storage-spring</artifactId>
    <version>2.3.0</version>
</dependency>
```

Then the application class needs the enabling annotation, and the configuration lives under a namespaced key in the application YAML:

```yaml
dromara:
  x-file-storage: #文件存储配置
    default-platform: aliyun-oss-1 #默认使用的存储平台
    aliyun-oss:
      - platform: aliyun-oss-1 # 存储平台标识
        enable-storage: true  # 启用存储
        access-key: ??
        secret-key: ??
        end-point: ??
        bucket-name: ??
        domain: ?? # 访问域名，注意“/”结尾，例如：https://abc.oss-cn-shanghai.aliyuncs.com/
        base-path: test/ # 基础路径
```

The comments are in the original. The structure is the thing to notice: a default platform name, and then a list of platform configurations grouped by type, each with an identifier. A platform identifier is what application code refers to, which is what makes the provider switchable. An application that says upload to the default platform keeps working when you change which platform that is, and an application that wants a specific one names it.

The trailing slash on the domain is called out in the comment and it is the kind of detail that costs an afternoon if you miss it, because a domain without a trailing slash produces URLs with a doubled or missing separator and nothing fails loudly.

The call itself:

```java
return fileStorageService.of(file).upload();
```

Everything else in the example is optional, and the README says so for each one. A path to store under, a filename rather than a generated one, an object identifier and an object type so a database row can find the file, and arbitrary attributes retrievable in aspects, in upload records and in custom platform implementations. The attributes call is the interesting one, because it is how you get metadata out of the storage layer and into your own code without the library knowing what it means.

The input types are wide, and the list is the design: a File, a multipart upload from a web request, a framework-specific uploaded file abstraction, a byte array, an input stream, a URL, a URI, a string and the servlet request object itself. Anything else can be added through a documented file adapter mechanism. So a caller that has bytes and a caller that has a URL both reach the same upload path, and a framework migration does not change your service layer.

## Image resizing and thumbnails, as an optional step on the same call

One feature in the upload example is a different kind of feature from the rest, and it is worth separating.

The image example is one call that resizes to a maximum dimension, generates a thumbnail at another size, and uploads. The processor used is a third-party Java image library, named and linked in a comment, which is a specific choice: the library does not implement image processing itself.

That is the right call. Image resizing is a solved problem with good solutions, and reimplementing it in a storage abstraction would mean handling colour profiles, EXIF orientation, animated formats and the dozen ways a browser disagrees with a decoder. Delegating to a named library is the correct engineering decision, and naming it in a comment is the correct documentation decision.

What it means for a user is that thumbnail support is a separate concern from storage support, in a way the fluent syntax does not make obvious. The image and thumbnail calls sit in the same chain as the upload, which reads as though they were the same kind of operation, and the practical effect is that the image processing dependency is needed whether or not you use it. The README's update plan lists thumbnails as still to come, which is confusing given the example shows a thumbnail call, and the honest reading is that the chained thumbnail is a convenience wrapper while a first-class thumbnail feature, presumably cached and served as a separate artefact, is planned.

The same chain also carries the set of optional modifiers from the previous section, so the full shape of a sophisticated upload is: a source, an optional path, an optional filename, optional object identity, optional attributes, optional image processing, an optional thumbnail, an optional target platform, and then the upload. Every one of those is optional, which is the API design principle in a single sentence.

What is not in the chain and is worth noting as a limitation is deletion, and the changelog gives the detail. The 2.2.0 release refactored presigned URL support to cover client-side upload, download and delete operations, so a browser can operate on a stored file directly without going through your server. That is a genuinely useful feature and it is also the one that hands a capability you were probably keeping server-side to a client, so the presigned URL's expiry configuration is a security decision and not a detail.

## Two framework integrations, and what a rename cost the people already using it

The repository is four Maven modules: a core, a Spring integration, a Solon integration and a tests module. Two framework integrations rather than one is the notable choice, because Solon is a much smaller ecosystem than Spring, and supporting both means the core is not a Spring library with a facade.

The README gives three usage paths: the Spring Boot default, using it in Solon, and using it standalone without Spring Boot at all. That third path is the one that says something about the design, because a library that works without any framework has to do its own configuration binding and its own bean wiring, and most Java libraries that claim to support Spring do not manage it.

The build files support the reading. There is a Maven wrapper, a project directory for a Java version manager pinned in the repository, and a Lombok configuration file at the top level, so the code uses Lombok's compile-time annotations rather than writing out accessors and constructors. That is a significant choice for a library, because Lombok is invisible in the compiled output and the tool must be configured in every consuming project. It also means the source you read is shorter than the source you write, and anyone contributing needs the annotation processing enabled.

The rename is the part an evaluator has to think about. At version 2.0.0 the project was donated to the dromara open source community, and the changelog records that it was renamed, its package names changed, its structure was reorganised, and metadata support was added, with a note that upgrading from the old version needs care. The old name was X Spring File Storage and it still has its own documentation site, and the new Maven coordinates are under a different group.

The two things that follow from that are mundane and both matter. An existing user has to change a group id and an artifact id, and the transitive behaviour of metadata is new, so a file uploaded after the upgrade has a different shape of record than one uploaded before. And a project that changes its coordinates has to change its coordinates, so any search for the old name in a codebase will find nothing about this one. The changelog is explicit that the upgrade needs care, which is the correct thing to say and more than most projects manage.

## The changelog tells you where the abstraction leaks

The release history is short, and reading it is more informative than the feature list, because bug fixes in a storage abstraction tell you which parts of it are genuinely uniform and which are not.

Version 2.3.0 added MongoDB GridFS, go-fastdfs, Amazon S3 V2 and Volcano Engine TOS, and the same entry says it fixed resource leaks and upload problems. A resource leak in a library that holds an input stream while uploading is a real defect in this kind of code, because the caller hands you a stream and you are responsible for closing it, and the temptation in every platform implementation is to wrap the caller's stream in your own and forget the original.

Version 2.2.1 fixed hash calculation errors in some cases, and fixed a case where a Qiniu Kodo presigned URL did not work. Both are worth reading twice. Hash calculation is a feature the library added, since the update plan and the 2.1.0 entry mention computing hashes, and hashes are how you deduplicate or verify an upload. A hash computed differently between platforms, or incorrectly on one, means two uploads of the same file produce two different hashes, which breaks exactly the use the feature exists for. And a presigned URL that does not work on one provider out of thirty is the clearest possible statement that the abstraction has edges.

Version 2.2.0 is the largest entry, adding fetching a file and listing files, refactoring presigned URL support for client-side upload, download and delete, adding the Solon plugin, and improving the manual chunked upload. Adding a list operation is a signal, because listing is the one thing storage platforms disagree about most. Object stores list by prefix, filesystem paths list by directory, GridFS lists by bucket and query, and a library that offers one list call across all of them has either normalised the semantics or picked the weakest common denominator.

Version 2.1.0 added two providers, copy and move with rename, manual chunked upload with resume, and hash computation. The move-with-rename capability is the other place the abstraction shows its seams, because renaming is trivial on a filesystem, a copy-and-delete on most object stores, and an explicit copy-then-delete API on some. The 2.3.0 release is the most recent, from June 2025, and the last push to the repository was on 2026-05-07, so the code is being touched well after the newest published artefact.

## Conclusion

Adopt x-file-storage if you are a Spring or Solon application on Java 8 or later that stores user uploads somewhere, and you want to change provider later without touching call sites, since the builder API and the return type are provider-independent. Do not adopt it expecting every platform to behave identically, because the two most recent changelog entries are fixes for hash calculation and for a presigned URL that did not work, which tells you the abstraction is real rather than complete. Verify first by configuring two platforms and switching between them on the same file, testing a multipart upload large enough to trigger the chunked path, and confirming that the domain value really does need its trailing slash, which the configuration comment calls out specifically.

## FAQ

### What is x-file-storage?

It is an Apache-2.0 Java library whose stated goal is storing a file with one line of code to local disk, FTP, SFTP, WebDAV or a long list of cloud and object storage providers. It also supports migrating files between storage platforms, and it was donated to the dromara community at version 2.0.0, where it added metadata support.

### Which storage platforms does x-file-storage support?

Local, FTP, SFTP and WebDAV, plus Aliyun OSS, Huawei Cloud OBS, Qiniu Kodo, Tencent COS, Baidu BOS, Upyun USS, MinIO, Amazon S3 and S3 V2, Google Cloud Storage, Cloudflare R2, Azure Blob Storage, FastDFS, MongoDB GridFS, go-fastdfs, Volcano Engine TOS, Kingsoft KS3, Meituan MSS, JD Cloud OSS, Tianyi Cloud OOS, Mobile Cloud EOS, Wo Cloud OSS, NetEase NOS, UCloud US3, QingCloud QingStor, Ping An Cloud OBS, IBM COS, and any other platform compatible with the S3 protocol.

### How do I use a consumer cloud drive such as Baidu Netdisk with x-file-storage?

By connecting to Alist over WebDAV. The README says that connecting to Alist over WebDAV lets you use Baidu Netdisk, Tianyi Cloud Drive, Aliyun Drive and Xunlei Netdisk through this library, and links to Alist's own list of the storage platforms it supports.

### How do I install and configure x-file-storage?

Add the framework integration dependency, in the example x-file-storage-spring at version 2.3.0, alongside whichever platform SDK you use, then configure it under the dromara.x-file-storage key in your application YAML with a default platform and per-platform credentials, then put the enabling annotation on your application class and call the service's upload method on a built request.

### What can I upload with x-file-storage?

A File, a multipart upload, a framework uploaded file abstraction, a byte array, an input stream, a URL, a URI, a string or the servlet request. Large files are uploaded in chunks automatically, and further input types can be added through the documented file adapter mechanism.

### What changed in x-file-storage version 2.0.0?

The project was donated to the dromara open source community, the project and package names changed, the structure was reorganised, and metadata support was added. The changelog notes that upgrading from the old version needs care, and the old documentation site for the previous name is still live.

## Sources

- [dromara/x-file-storage on GitHub](https://github.com/dromara/x-file-storage)
- [License: Apache-2.0](https://github.com/dromara/x-file-storage/blob/main/LICENSE)
- [Project website](https://x-file-storage.xuyanwu.cn/)
- [README](https://github.com/dromara/x-file-storage/blob/main/README.md)
- [Releases](https://github.com/dromara/x-file-storage/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/dromara-x-file-storage
