The problem, briefly
Out-of-the-box ACS allocates the whole file in repository heap before streaming a download, and fails around 1 GB. This was documented at Alfresco DevCon 2018 in Jeff Potts talk "Moving Gigantic Files In & Out of the Repository". The ADF Download Manager fixes it with a paired server + client approach: a repository addon that streams from the content store in bounded buffers, and a frontend library that chunks downloads via HTTP Range and stages them to disk instead of browser memory.
This post skips the problem statement (see the project README for the full write-up) and goes straight to how you wire it into an ADF host, what it costs to operate, and what it looks like running.
Two deliverables, two repositories
- Frontend library (
@alfresco/adf-download-manager) - an Angular library, source-only for now, in this repository. All UI and the download/upload engine live here. - Repository addon - a headless ACS web script, published separately at alfresco-download-streaming-repo. It ships as a prebuilt JAR on GitHub Releases, not as source you build locally.
The frontend works without the addon (it falls back to chunked Range requests against the stock content API), but pairs best with it: the addon defeats the ~1 GB ceiling at the source, so the JVM heap never holds more than a small buffer regardless of file size.
Integrating the library: ACA and ADW use the same mechanism
The library doesn't hook into ACA-specific internals. It registers through @alfresco/adf-extensions - the same declarative extensibility framework both ACA (Community) and ADW (Enterprise) consume for their toolbar, context menu, and sidebar plugins. If your host app loads extensions the standard ADF way, integration is three steps:
1. Register the provider:
// app.config.ts or extensions.module.ts
import { provideAdfDownloadManagerExtension } from '@alfresco/adf-download-manager';
export const appConfig: ApplicationConfig = {
providers: [
// ... your existing providers
...provideAdfDownloadManagerExtension()
]
};
2. Ship its assets (i18n files and the plugin descriptor) via your angular.json asset globs - see the Integration Guide for the exact glob entries.
3. That's it. adf-download-manager.plugin.json declares the toolbar buttons, context-menu entries, and sidebar tabs using ADF's extension schema - the same $id/actions/features shape any ADF extension uses:
{
"actions": [
{ "id": "adf-download-manager.download-file", "type": "ADF_DOWNLOAD_MANAGER_DOWNLOAD_FILE" }
],
"features": {
"toolbar": [ /* download button, visible when a file is selected */ ],
"contextMenu": [ /* right-click "Download" */ ],
"sidebar": { "tabs": [ /* Downloads and Uploads panels */ ] }
}
}
Where ACA and ADW actually differ: nowhere in this mechanism. Both are @alfresco/adf-extensions hosts, so the same three steps apply to either. What hasn't happened yet: this repository only builds and tests against ACA (see deployment/ and aca/). Nothing here has been run against a real ADW instance. If you're on ADW, the integration steps are identical by design - but treat that as "should work," not "verified," until someone runs it and reports back.
Running it
The addon ships as a release artifact, not a build step
deployment/alfresco/Dockerfile fetches the addon directly from GitHub Releases at image-build time:
ARG ADDON_VERSION=1.0.0
FROM alfresco/alfresco-content-repository-community:${ACS_TAG}
ARG ADDON_JAR_URL=https://github.com/aborroy/alfresco-download-streaming-repo/releases/download/v${ADDON_VERSION}/alfresco-download-streaming-repo-${ADDON_VERSION}.jar
ADD --chown=alfresco:Alfresco --chmod=644 ${ADDON_JAR_URL} /usr/local/tomcat/webapps/alfresco/WEB-INF/lib/alfresco-download-streaming-repo.jar
No local Maven build, no JDK on the host. To pick up a new addon release, bump ADDON_VERSION and rebuild the alfresco image. The addon has no Enterprise dependency - it's a plain repository JAR that works against Community or Enterprise ACS equally, so this same Dockerfile pattern applies whether your frontend is ACA or ADW.
Your reverse proxy has to stream, not buffer
The most common way this breaks in production isn't the app, it's the proxy in front of it. Whatever reverse proxy sits in front of ACS needs to pass request/response bodies through without buffering the whole thing in RAM - a buffering limit middleware (Traefik) or an unbounded proxy_buffering (nginx) will OOM-kill the proxy on multi-GB transfers, independent of anything the addon or the library do correctly. The demo stack's deployment/compose.yaml shows the Traefik-side fix: no buffering middleware on the route, and generous respondingTimeouts (1 h) so slow transfers aren't cut off mid-stream.
ACS itself needs no size limit set
-Dsystem.content.maximumFileSizeLimit=0 on the repository, so ACS doesn't reject large uploads before the addon or library ever get involved.
Demo: running the stack, on ACA/Community
The repo ships a self-contained Docker Compose stack - Traefik, a custom ACA build with the extension baked in, and ACS Community 26.1 - that starts with one command and needs no host Maven/Node/JDK:
cd deployment
docker compose up --build -d
A few screenshots from a real run against this stack
A multi-gigabyte test file sitting in Personal Files, ready to download.
The Downloads panel: progress bar, transferred/total size, speed, and ETA - updated in real time as the chunked Range loop advances.
Paused mid-transfer. The partial bytes stay on disk (OPFS); resume continues from the exact byte offset, not from zero.
What's actually on the wire, captured from the same run: sequential 64 MB Range: bytes=X-Y requests, each answered with 206 Partial Content - the mechanism behind both the chunking and the resume.
This demo is Community-only; there's no ADW deployment in this repository to screenshot. Since the extension mechanism is identical, the UI on ADW should look the same.