---
title: "Open PDF from URL on Android | Nutrient SDK"
canonical_url: "https://www.nutrient.io/guides/android/miscellaneous/document-downloads/"
md_url: "https://www.nutrient.io/guides/android/miscellaneous/document-downloads.md"
last_updated: "2026-05-21T20:35:00.917Z"
description: "Open PDF documents from any URL on Android using Nutrient Android SDK. Learn to implement different methods for downloading and managing remote files efficiently."
---

# Open PDFs from URLs on Android

Nutrient usually works best with PDF documents on the local file system of your device. While you can also open documents from practically any remote source [using a custom data provider](https://www.nutrient.io/guides/android/features/data-providers.md), there are several reasons — such as performance, cache control, and battery impact — to use local file system documents. Our SDK offers several means for opening a remote document, with different levels of control for how to deal with the downloaded file. Let’s start with the easiest method and work our way up.

You can find additional example code inside `DocumentDownloadExample` and `RemoteUrlExample` in the Catalog app.

## 1. Use the URL as you would to open a local file with a Uri

Our SDK checks the scheme of the provided `Uri` for `http` or `https` and will replace it internally with a [`UrlDataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-url-data-provider/index.html). The file will be downloaded into your app’s cache folder and opened from there. The next time you open the same URL, it’ll be downloaded again, and the previously cached file will be overwritten. This mechanism works with any of the following methods that accept either a `Uri` or a [`DocumentSource`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document/-document-source/index.html) (which can wrap a `Uri`).

To load a PDF, use:

- [`PdfActivityIntentBuilder.fromUri`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-activity-intent-builder/from-uri.html)

- [`PdfUiFragmentBuilder.fromUri`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-ui-fragment-builder/from-uri.html)

- [`PdfDocumentLoader.openDocument`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document/-pdf-document-loader/open-document.html)

- [`PdfDocumentLoader.openDocumentAsync`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document/-pdf-document-loader/open-document-async.html)

For image documents, use:

- [`PdfActivityIntentBuilder.fromImageUri`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-activity-intent-builder/from-image-uri.html)

- [`PdfUiFragmentBuilder.fromImageUri`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-ui-fragment-builder/from-image-uri.html)

- [`ImageDocumentLoader.openDocument`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document/-image-document-loader/open-document.html)

- [`ImageDocumentLoader.openDocumentAsync`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document/-image-document-loader/open-document-async.html)

Our SDK doesn’t delete any of the downloaded files, since the OS will purge the the cache folder automatically if it’s low on resources. If you want to clean up yourself, call:

- `UrlDataProvider.deleteCachedFileForUrl(url)` to remove the download of a specific URL.

- `UrlDataProvider.deleteCachedFiles` to clear all downloads.

## 2. Direct use of a UrlDataProvider

As mentioned in the previous section, passing a remote URL will indirectly make use of [`UrlDataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-url-data-provider/index.html). But you can use it directly, too. This enables you to provide a `File` object for storing the downloaded file. Nutrient Android SDK reuses this file if it already exists and avoids downloading it when it’s already locally available. Ensure the app has read/write access to the file path.

Create an instance of [`UrlDataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-url-data-provider/index.html) like this:

### KOTLIN

```kotlin

// We use a `File` object for easy parsing of the filename from the URL.
val file = File(urlString)
val provider = UrlDataProvider(URL(urlString), File(context.filesDir, "myDownloads/${file.name}"))

```

### JAVA

```java

// We use a `File` object for easy parsing of the filename from the URL.
final var file = new File(urlString);
final var provider = new UrlDataProvider(new URL(urlString), new File(context.getFilesDir(), "myDownloads/" + file.getName()));

```

`PdfDocumentLoader` and `ImageDocumentLoader` don’t support data providers directly, but you can wrap them into a [`DocumentSource`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document/-document-source/index.html).

### KOTLIN

```kotlin

PdfDocumentLoader.openDocument(context, DocumentSource(dataProvider))

```

### JAVA

```java

PdfDocumentloader.openDocument(context, new DocumentSource(dataProvider));

```

To load a PDF, use:

- [`PdfActivityIntentBuilder.fromDataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-activity-intent-builder/from-data-provider.html)

- [`PdfUiFragmentBuilder.fromDataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-ui-fragment-builder/from-data-provider.html)

- [`PdfDocumentLoader.openDocument`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document/-pdf-document-loader/open-document.html)

- [`PdfDocumentLoader.openDocumentAsync`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document/-pdf-document-loader/open-document-async.html)

For image documents, use:

- [`PdfActivityIntentBuilder.fromImageProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-activity-intent-builder/from-image-provider.html)

- [`PdfUiFragmentBuilder.fromImageProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-ui-fragment-builder/from-image-provider.html)

- [`ImageDocumentLoader.openDocument`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document/-image-document-loader/open-document.html)

- [`ImageDocumentLoader.openDocumentAsync`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document/-image-document-loader/open-document-async.html)

### Important: Don’t block your main thread!

This information applies to both the direct and indirect use of `UrlDataProvider`:

If you call any of the `PdfDocumentLoader`/`ImageDocumentLoader` open methods — regardless of if they’re async or not — with a remote URL on the main thread, you’ll run into a `DownloadOnMainThreadException`. This is because it would block the main thread until the document is downloaded and loaded. And the general consensus is this is a bad idea.

That said, if you’re **absolutely sure** that blocking the main thread with a download operation is a good idea, you can still make a blocking call that bypasses the exception with `:\`openDocumentAsync(...).subscribeOn(`_`<any scheduler but main>`_`).blockingGet()`or`...blockingSubscribe(...)`.

## 3. Download the file on your own

### Creating a download request

Before initiating a download, create a [`DownloadRequest`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-download-request/index.html) object that defines all information required to perform the download. Using the [`DownloadRequest.Builder`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-download-request/-builder/index.html), you can set the download source, the output file or folder, whether existing files should be overwritten, etc. A call to [`Builder#build`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-download-request/-builder/build.html) will then output the immutable [`DownloadRequest`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-download-request/index.html) object:

### KOTLIN

```kotlin

val request: DownloadRequest = DownloadRequest.Builder(context).uri("content://com.example.app/documents/example.pdf").build()

```

### JAVA

```java

final DownloadRequest request = new DownloadRequest.Builder(context).uri("content://com.example.app/documents/example.pdf").build();

```

The [`DownloadRequest.Builder#uri`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-download-request/-builder/uri.html) method can handle URIs pointing to content providers or to a file in the app’s assets (i.e. URIs starting with `file:///android_asset/`).

### Starting the download

Once you’ve created a [`DownloadRequest`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-download-request/index.html), start the download by passing it to [`DownloadJob#startDownload`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-download-job/start-download.html):

### KOTLIN

```kotlin

val job: DownloadJob = DownloadJob.startDownload(request)

```

### JAVA

```java

final DownloadJob job = DownloadJob.startDownload(request);

```

The [`DownloadJob`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-download-job/index.html) object has three responsibilities:

- Holding a task that will handle downloading the PDF file from the source defined in the request to the file system.

- Providing methods for observing and controlling the download progress.

- By retaining the [`DownloadJob`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-download-job/index.html) instance across configuration changes, you can keep the download job alive and prevent it from being interrupted.

### Observing the download progress

The download job will continuously report the download progress using [`Progress`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-progress/index.html) instances. You can set a [`ProgressListener`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-download-job/-progress-listener/index.html) via [`DownloadJob#setProgressListener`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-download-job/set-progress-listener.html), which will observe progress, completion, and download errors. All methods of the listener are called on the main thread. This means you can update your UI directly from these methods. Just be aware that you shouldn’t invoke longer-running operations or access the network without switching to the background thread.

While there can only be a single [`ProgressListener`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-download-job/-progress-listener/index.html) per job, you can register as many observers to the [`Observable<Progress>`](http://reactivex.io/RxJava/javadoc/rx/Observable.html) as you’d like.

### KOTLIN

```kotlin

job.setProgressListener(object : DownloadJob.ProgressListenerAdapter() {
    override fun onProgress(progress: Progress) {
        progressBar.setProgress((100f * progress.bytesReceived / progress.totalBytes).toInt())
    }

    override fun onComplete(output: File) {
        PdfActivity.showDocument(context, Uri.fromFile(output), configuration.build())
    }

    override fun onError(exception: Throwable) {
        handleDownloadError(exception)
    }
})

```

### JAVA

```java

job.setProgressListener(new DownloadJob.ProgressListenerAdapter() {
    @Override public void onProgress(@NonNull Progress progress) {
        progressBar.setProgress((int) (100 * progress.bytesReceived / (float) progress.totalBytes));
    }

    @Override public void onComplete(@NonNull File output) {
        PdfActivity.showDocument(context, Uri.fromFile(output), configuration.build());
    }

    @Override public void onError(@NonNull Throwable exception) {
        handleDownloadError(exception);
    }
});

```

### Using a progress observable

If you prefer more control over how to receive [`Progress`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-progress/index.html) events, you can retrieve an RxJava [`Observable<Progress>`](http://reactivex.io/RxJava/javadoc/rx/Observable.html). Similar to the [`ProgressListener`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-download-job/-progress-listener/index.html), the observable will emit [`Progress`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-progress/index.html) events, as well as completion and error events. Events are received on a background thread by default, so your code needs to handle switching to the main thread on its own:

### KOTLIN

```kotlin

job.progress.observeOn(AndroidSchedulers.mainThread()).subscribe(object : Subscriber<Progress>() {
        override fun onCompleted() {
            PdfActivity.showDocument(context, Uri.fromFile(output), configuration.build())
        }

        override fun onError(e: Throwable) {
            handleDownloadError(e)
        }

        override fun onNext(progress: Progress) {
            progressBar.setProgress((100f * progress.bytesReceived / progress.totalBytes).toInt())
        }
    })

```

### JAVA

```java

job.getProgress().observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<Progress>() {
        @Override public void onCompleted() {
            PdfActivity.showDocument(context, Uri.fromFile(output), configuration.build());
        }

        @Override public void onError(Throwable e) {
            handleDownloadError(exception);
        }

        @Override public void onNext(Progress progress) {
            progressBar.setProgress((int) (100 * progress.bytesReceived / (float) progress.totalBytes));
        }
    });

```

Since progress events are non-critical, the progress observable will automatically handle back pressure by dropping excessive events.

## 4. Implementing a custom download source

If you want to download a PDF from a custom source, e.g. the web, you can use your own [`DownloadSource`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download.source/-download-source/index.html) implementation. The `open()`] method has to return an [`InputStream`](https://developer.android.com/reference/java/io/InputStream.html) instance that will return the complete content of the PDF file. The `getLength()` method has to return either the download size in bytes, or [`DownloadSource#UNKNOWN_DOWNLOAD_SIZE`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download.source/-download-source/-u-n-k-n-o-w-n_-d-o-w-n-l-o-a-d_-s-i-z-e.html) if the size isn’t known. The returned size is optional, since it’s only used for creating higher quality [`Progress`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-progress/index.html) events.

Here’s an example of a [`DownloadSource`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download.source/-download-source/index.html) that can download a PDF file from the web:

### KOTLIN

```kotlin

class WebDownloadSource (private val documentURL: URL) : DownloadSource {
    override fun open(): InputStream {
        val connection = documentURL.openConnection() as HttpURLConnection
        connection.connect()
        return connection.inputStream
    }

    override fun getLength(): Long {
        var length = DownloadSource.UNKNOWN_DOWNLOAD_SIZE

        try {
            val contentLength = documentURL.openConnection().contentLength
            if (contentLength!= -1) {
                length = contentLength.toLong()
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }

        return length
    }
}

```

### JAVA

```java

private static class WebDownloadSource implements DownloadSource {
    @NonNull private final URL documentURL;

    private WebDownloadSource(@NonNull URL documentURL) {
        this.documentURL = documentURL;
    }

    @Override public InputStream open() throws IOException {
        final HttpURLConnection connection = (HttpURLConnection) documentURL.openConnection();
        connection.connect();
        return connection.getInputStream();
    }

    @Override public long getLength() {
        long length = DownloadSource.UNKNOWN_DOWNLOAD_SIZE;

        try {
            final int contentLength = documentURL.openConnection().getContentLength();
            if (contentLength!= -1) {
                length = contentLength;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return length;
    }
}

```

## Retaining the download

To keep downloads running uninterrupted when your activity is recreated during a configuration change, you need to retain the [`DownloadJob`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.download/-download-job/index.html) instance of your download. The simplest way to do this is to store the instance inside a retained [`Fragment`](https://developer.android.com/reference/android/support/v4/app/Fragment.html) and retrieve it after the activity has been recreated:

### MYACTIVITY.KT

```kt

class MyActivity : AppCompatActivity(), DownloadJob.ProgressListener {

    /**
     * A non-UI fragment for retaining the download job.
     */
    class DownloadFragment : Fragment() {
        var job: DownloadJob? = null

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)

            // Retaining this fragment allows it to carry the attached download job across configuration changes.
            retainInstance = true
        }
    }

    /**
     * Fragment for holding and retaining the current download job.
     */
    private var downloadFragment: DownloadFragment? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // This will return an existing fragment if the activity was recreated while a download was running.
        downloadFragment = supportFragmentManager.findFragmentByTag(DOWNLOAD_FRAGMENT) as DownloadFragment
        downloadFragment?.job?.setProgressListener(this)
    }

    override fun onDestroy() {
        super.onDestroy()

        // Clear the listener to prevent activity leaks.
        downloadFragment?.job?.setProgressListener(null)
    }

    /**
     * This method will initiate the download.
     */
    private fun startDownload() {
        if (downloadFragment == null) {
            downloadFragment = DownloadFragment()

            // Once the fragment is added, the download is safely retained.
            supportFragmentManager.beginTransaction().add(downloadFragment, DOWNLOAD_FRAGMENT).commit()
        }

        val request = DownloadRequest.Builder(this).uri("content://com.example.app/documents/example.pdf").build()

        val job = DownloadJob.startDownload(request)
        job.setProgressListener(this)
        downloadFragment?.job = job
    }

    override fun onProgress(progress: Progress) {
        // Update your UI.
    }

    override fun onComplete(output: File) {
        supportFragmentManager.beginTransaction().remove(downloadFragment).commit()

        // Handle the finished download.
    }

    override fun onError(exception: Throwable) {
        // Report and handle download errors.
    }

    companion object {
        /**
         * Tag of the retained fragment.
         */
        internal val DOWNLOAD_FRAGMENT = "download_fragment"
    }
}

```

### MYACTIVITY.JAVA

```java

public class MyActivity extends AppCompatActivity implements DownloadJob.ProgressListener {

    /**
     * A non-UI fragment for retaining the download job.
     */
    public final static class DownloadFragment extends Fragment {
        public DownloadJob job;

        @Override public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            // Retaining this fragment allows it to carry the attached download job across configuration changes.
            setRetainInstance(true);
        }
    }

    /**
     * Tag of the retained fragment.
     */
    final static String DOWNLOAD_FRAGMENT = "download_fragment";

    /**
     * Fragment for holding and retaining the current download job.
     */
    private DownloadFragment downloadFragment;

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // This will return an existing fragment if the activity was recreated while a download was running.
        downloadFragment = (DownloadFragment) getSupportFragmentManager().findFragmentByTag(DOWNLOAD_FRAGMENT);
        if (downloadFragment!= null) {
            downloadFragment.job.setProgressListener(this);
        }
    }

    @Override protected void onDestroy() {
        super.onDestroy();

        // Clear the listener to prevent activity leaks.
        if (downloadFragment!= null) {
            downloadFragment.job.setProgressListener(null);
        }
    }

    /**
     * This method will initiate the download.
     */
    private void startDownload() {
        if (downloadFragment == null) {
            downloadFragment = new DownloadFragment();

            // Once the fragment is added, the download is safely retained.
            getSupportFragmentManager().beginTransaction().add(downloadFragment, DOWNLOAD_FRAGMENT).commit();
        }

        final DownloadRequest request = new DownloadRequest.Builder(this).uri("content://com.example.app/documents/example.pdf").build();

        downloadFragment.job = DownloadJob.startDownload(request);
        downloadFragment.job.setProgressListener(this);
    }

    @Override public void onProgress(@NonNull Progress progress) {
        // Update your UI.
    }

    @Override public void onComplete(@NonNull File output) {
        getSupportFragmentManager().beginTransaction().remove(downloadFragment).commit();

        // Handle the finished download.
    }

    @Override public void onError(@NonNull Throwable exception) {
        // Report and handle download errors.
    }
}

```
---

## Related pages

- [Open a PDF file from Document Engine on Android](/guides/android/open-a-document/from-document-engine.md)
- [Open PDFs from a custom data provider on Android](/guides/android/features/data-providers.md)
- [Open PDFs from in-memory data on Android](/guides/android/open-a-document/from-in-memory-data.md)
- [Open PDF files on Android](/guides/android/open-a-document.md)
- [Open a local PDF file on Android](/guides/android/open-a-document/from-local-storage.md)
- [Open password-protected PDFs on Android](/guides/android/security/secured-documents.md)

