Bulk Move Assets Using Asset Manager Granite API (High‑Volume Moves) | Community
Skip to main content
Level 3
March 18, 2026
Question

Bulk Move Assets Using Asset Manager Granite API (High‑Volume Moves)

  • March 18, 2026
  • 2 replies
  • 7 views

Hi,

I am working on an AEM servlet/workflow that needs to bulk move assets using the Asset Manager Granite API.
 

Key Requirement:
The solution must reliably handle the movement of 300–500 assets in a single execution without errors such as repository exceptions, session timeouts, or failed moves. Performance and stability during high‑volume moves are crucial.

I am looking for guidance on the following points:

  1. Feasibility

    • Is it supported and recommended to move 300–500 assets at a time using the Granite Asset Manager API via a servlet?
    • Are there any known limits, pitfalls, or configuration considerations when moving assets at this scale?
  2. Best Practices / Algorithm

    • What is the recommended approach or algorithm for bulk asset movement to avoid errors?
    • Should assets be moved in batches, and if so, what batch size is considered safe?
    • Are there recommendations around session handling, save frequency, or throttling to ensure stability?
  3. Destination Path Validation

    • Before moving assets, the servlet needs to check whether the destination path exists.
    • The destination folder may be created by another API.
    • If the destination path does not exist, the servlet should create the folder programmatically and then proceed with the move operation.
  4. Sample Code / References

    • Are there any sample implementations or reference code that demonstrate:
      • Destination path validation
      • Folder creation (DAM folders)
      • Bulk asset movement using the Granite Asset Manager API
      • Handling large volumes (300–500 assets) safely in a servlet

Thanks & Regards,
Vishal Jain

2 replies

AmitVishwakarma
Community Advisor
Community Advisor
March 18, 2026

Hi ​@Vishal33 

Yes, moving 300–500 assets in one execution via the Granite AssetManager API is feasible and supported, as long as you handle it in batches and commit changes regularly.

1. Feasibility & limits

  • Moving 300–500 assets per run is fine if you:
    • Do not move them all in a single commit.
    • Use small batches (e.g. 50–100 assets per batch).
  • Avoid very large folders; for usability and performance, keep folders to a reasonable number of assets and prefer sling:Folder over sling:OrderedFolder for large collections.

2. Recommended algorithm for bulk moves

Suggested pattern (works well for 300–500 assets):

  • Get a ResourceResolver/Session with DAM write permissions (service user in servlet, workflow session in a process step).
  • Ensure the destination folder exists (create programmatically if needed).
  • Split the list of source asset paths into batches, e.g. BATCH_SIZE = 50.
  • For each batch:
    • For each asset:
      • Validate the source path exists and is a dam:Asset.
      • Build the destination path under the destination folder.
      • Call assetManager.moveAsset(srcPath, destPath).
    • After each batch:
      • Call resolver.commit() (or session.save()).
      • Optionally resolver.refresh().
    • Close the ResourceResolver/Session in a finally block.

This minimizes the risk of long‑running commits, repository exceptions, and session timeouts.

3. Destination path validation & DAM folder creation

Below is a simplified example you can adapt in a servlet or workflow step.

import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.AssetManager;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.jackrabbit.JcrConstants;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BulkAssetMover {

private static final int BATCH_SIZE = 50;

public void moveAssets(ResourceResolver resolver,
List<String> sourceAssetPaths,
String destFolderPath) throws PersistenceException {

AssetManager assetManager = resolver.adaptTo(AssetManager.class);
if (assetManager == null) {
throw new IllegalStateException("Cannot adapt to AssetManager");
}

// 1) Ensure destination folder exists
ensureDamFolder(resolver, destFolderPath);

List<String> batch = new ArrayList<>(BATCH_SIZE);

for (String src : sourceAssetPaths) {
batch.add(src);

if (batch.size() >= BATCH_SIZE) {
moveBatch(assetManager, batch, destFolderPath);
resolver.commit(); // one commit per batch
resolver.refresh();
batch.clear();
}
}

// Remaining items
if (!batch.isEmpty()) {
moveBatch(assetManager, batch, destFolderPath);
resolver.commit();
resolver.refresh();
}
}

private void moveBatch(AssetManager assetManager,
List<String> batch,
String destFolderPath) {

for (String srcPath : batch) {
Asset srcAsset = assetManager.getAsset(srcPath);
if (srcAsset == null) {
// log and skip invalid paths
continue;
}

String name = ResourceUtil.getName(srcPath);
String destPath = destFolderPath + "/" + name;

assetManager.moveAsset(srcPath, destPath);
}
}

/**
* Creates a DAM folder (/content/dam/...) if missing.
* Uses sling:Folder (unordered) which scales better for many children.
*/
private void ensureDamFolder(ResourceResolver resolver,
String folderPath) throws PersistenceException {

Resource folder = resolver.getResource(folderPath);
if (folder != null) {
return;
}

Map<String, Object> props = new HashMap<>();
props.put(JcrConstants.JCR_PRIMARYTYPE, "sling:Folder");

// getOrCreateResource will also create intermediate folders when needed
ResourceUtil.getOrCreateResource(
resolver,
folderPath,
props,
null,
true
);
resolver.commit();
}
}

4. Note

  • Batch size: 50–100 assets per batch is usually safe for typical environments.
  • Save frequency: Always commit()/save() after each batch instead of once at the end.
  • Session handling: Use a service user; never use the request user session in a long‑running servlet.
  • Error handling:
    • Log and skip assets that do not exist or are not dam:Asset.
    • Optionally collect failures into a report so you can retry them.
Amit Vishwakarma - Adobe Commerce Champion 2025 | 16x Adobe certified | 4x Adobe SME
Vishal33Author
Level 3
March 18, 2026

Thanks ​@AmitVishwakarma 
For a quick reply...
we will check it

Thanks & Regards,
Vishal Jain

sarav_prakash
Community Advisor
Community Advisor
March 18, 2026

Hey ​@Vishal33 , I worked on a massive dam migration project and documented my learnings in my blog

 

TL;DR Use OOTB Bulk Importer or Bulk Import Assets API.

 

First attempt - using Old Java APIs - https://medium.com/@bsaravanaprakash/how-we-migrated-a-million-assets-into-aem-cloud-service-dam-ef7eb74d30fa

Second attempt - via AIO - https://medium.com/@bsaravanaprakash/building-content-supply-chain-using-workfront-aio-journaling-appbuilder-runtime-actions-finally-4d7a6ca0fbf2

 

We faced multiple issues with volume, publishing, syncing to DM etc. Lessons learnt was, if source is aws, google, sharepoint cloud, blindly run bulk importer. Else build a job outside AEM on AppBuilder and import Cloud-native way. Using Java APIs will easily become bottleneck. My server production crashed multiple times, until I hijacked outside AEM. I ll answer invidual questions

 

  1. Feasibility

    • Is it supported and recommended to move 300–500 assets at a time using the Granite Asset Manager API via a servlet? - Yes
    • Are there any known limits, pitfalls, or configuration considerations when moving assets at this scale? - JVM has to first download binary inmemory to JVM and then reupload into jcr. Drastically slows AEM, clogs distribution queue, and will sure crash JVM, when it hits memory limits 
  2. Best Practices / Algorithm

    • What is the recommended approach or algorithm for bulk asset movement to avoid errors? - use ootb bulk importer / bulk assets api
    • Should assets be moved in batches, and if so, what batch size is considered safe? Appbuilder provides excellent Journalling queue based of Kafka. You read messages from queue, if fails, push back to queue, so will retry after others are complete. 
    • Are there recommendations around session handling, save frequency, or throttling to ensure stability? Setup monitors by log forwarding from AppBuilder. I manually chunked payloads and imported a million assets in controlled way. Took ~ 1month
  3. Destination Path Validation

    • Before moving assets, the servlet needs to check whether the destination path exists. - both bulk import / api handles this. you can configure to replace / create version. 
    • The destination folder may be created by another API.
    • If the destination path does not exist, the servlet should create the folder programmatically and then proceed with the move operation. - preferably dont do MOVEs, Its even slow than delete-create new
  4. Sample Code / References

    • Are there any sample implementations or reference code that demonstrate:
      • Destination path validation
      • Folder creation (DAM folders)
      • Bulk asset movement using the Granite Asset Manager API
      • Handling large volumes (300–500 assets) safely in a servlet - my articles

Happy to answer any specific questions. But definitely your project requires, planning, prototyping, workforce todo esp dealing with bulk asset operations. 

 

Thanks

Saravana