brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
replicationPublish, deactivate, and delete AEM content programmatically via the Cloud Service Replication API (com.day.cq.replication). Covers single-path and bulk activation, preview-tier targeting with AgentFilter, synchronous/async options, ReplicationStatus checks, permission validation, workflow process steps, and replication event handling. Use when the user nee…
Independent GitHub snapshot · Automated classification · Original documentation governs
Programmatic content publishing and unpublishing using the official AEM Replication API.
Use the Replication API for programmatic content distribution:
For UI-based publishing, use Manage Publication or Quick Publish instead.
Key Classes:
com.day.cq.replication.Replicator - Main replication servicecom.day.cq.replication.ReplicationOptions - Configuration optionscom.day.cq.replication.ReplicationStatus - Publication statuscom.day.cq.replication.ReplicationActionType - Action types (ACTIVATE, DEACTIVATE, DELETE, TEST)import com.day.cq.replication.Replicator;
import com.day.cq.replication.ReplicationActionType;
import com.day.cq.replication.ReplicationException;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import javax.jcr.Session;
@Component(service = ContentPublisher.class)
public class ContentPublisher {
@Reference
private Replicator replicator;
/**
* Activate a single page to Publish tier
*/
public void publishPage(Session session, String pagePath)
throws ReplicationException {
replicator.replicate(
session,
ReplicationActionType.ACTIVATE,
pagePath
);
}
/**
* Deactivate a page from Publish tier
*/
public void unpublishPage(Session session, String pagePath)
throws ReplicationException {
replicator.replicate(
session,
ReplicationActionType.DEACTIVATE,
pagePath
);
}
/**
* Delete content from Publish tier
*/
public void deleteFromPublish(Session session, String path)
throws ReplicationException {
replicator.replicate(
session,
ReplicationActionType.DELETE,
path
);
}
}
CONSTRAINTS (per official Javadoc):
Note:
ReplicationOptions.setUseAtomicCalls()is@Deprecatedand marked "no longer required" in the Cloud Service Javadoc. Do not call it; the system handles auto-bucketing automatically.
public class BulkPublisher {
@Reference
private Replicator replicator;
/**
* Publish multiple pages.
* ≤100 paths → replicated atomically (transactional guarantee).
* >100 paths → system automatically splits into non-transactional chunks.
*/
public void publishMultiplePages(Session session, String[] pagePaths)
throws ReplicationException {
replicator.replicate(
session,
ReplicationActionType.ACTIVATE,
pagePaths,
null // No options needed; system handles auto-bucketing above 100 paths
);
}
}
Best Practice: For large hierarchical content trees, use the Tree Activation workflow step instead of custom code.
The Preview tier requires explicit agent filtering.
import com.day.cq.replication.Agent;
import com.day.cq.replication.AgentFilter;
public class PreviewPublisher {
@Reference
private Replicator replicator;
/**
* Publish to Preview tier only
*/
public void publishToPreview(Session session, String pagePath)
throws ReplicationException {
ReplicationOptions options = new ReplicationOptions();
// Filter to target Preview agent only
options.setFilter(new AgentFilter() {
@Override
public boolean isIncluded(Agent agent) {
return "preview".equals(agent.getId());
}
});
replicator.replicate(
session,
ReplicationActionType.ACTIVATE,
pagePath,
options
);
}
/**
* Publish to BOTH Preview and Publish tiers
*/
public void publishToBothTiers(Session session, String pagePath)
throws ReplicationException {
ReplicationOptions options = new ReplicationOptions();
// Include both preview and publish agents
options.setFilter(new AgentFilter() {
@Override
public boolean isIncluded(Agent agent) {
String agentId = agent.getId();
return "preview".equals(agentId) || "publish".equals(agentId);
}
});
replicator.replicate(
session,
ReplicationActionType.ACTIVATE,
pagePath,
options
);
}
}
Note: Preview agent is disabled by default and must be configured in Cloud Manager.
public class SynchronousPublisher {
@Reference
private Replicator replicator;
/**
* Synchronous replication (wait for completion)
*/
public void publishSynchronously(Session session, String pagePath)
throws ReplicationException {
ReplicationOptions options = new ReplicationOptions();
options.setSynchronous(true); // Block until complete
// Optional: Add listener for synchronous feedback
options.setListener(new ReplicationListener() {
@Override
public void onStart(ReplicationAction action) {
// Called when replication starts
}
@Override
public void onMessage(ReplicationLog.Level level, String message) {
// Called for progress updates
}
@Override
public void onEnd(ReplicationAction action, ReplicationResult result) {
// Called when replication completes
}
@Override
public void onError(ReplicationAction action, Exception error) {
// Called if replication fails
}
});
replicator.replicate(
session,
ReplicationActionType.ACTIVATE,
pagePath,
options
);
}
}
Important: ReplicationListener only works with synchronous replication.
/**
* Replicate without updating replication metadata properties
*/
public void replicateWithoutStatusUpdate(Session session, String path)
throws ReplicationException {
ReplicationOptions options = new ReplicationOptions();
options.setSuppressStatusUpdate(true); // Don't update cq:lastReplicated, etc.
replicator.replicate(
session,
ReplicationActionType.ACTIVATE,
path,
options
);
}
/**
* Replicate without creating implicit versions
*/
public void replicateWithoutVersioning(Session session, String path)
throws ReplicationException {
ReplicationOptions options = new ReplicationOptions();
options.setSuppressVersions(true); // Don't auto-version on replication
replicator.replicate(
session,
ReplicationActionType.ACTIVATE,
path,
options
);
}
import com.day.cq.replication.ReplicationStatus;
import org.apache.sling.api.resource.Resource;
public class StatusChecker {
@Reference
private Replicator replicator;
/**
* Check if content is currently published
*/
public boolean isPublished(Session session, String path)
throws ReplicationException {
ReplicationStatus status = replicator.getReplicationStatus(session, path);
if (status == null) {
return false; // No replication metadata
}
return status.isActivated();
}
/**
* Get detailed replication information
*/
public void printReplicationInfo(Session session, String path)
throws ReplicationException {
ReplicationStatus status = replicator.getReplicationStatus(session, path);
if (status != null) {
System.out.println("Activated: " + status.isActivated());
System.out.println("Deactivated: " + status.isDeactivated());
System.out.println("Last Published: " + status.getLastPublished());
System.out.println("Published By: " + status.getLastPublishedBy());
System.out.println("Last Action: " + status.getLastReplicationAction());
System.out.println("Pending: " + status.isPending());
}
}
/**
* Alternative: Adapt resource to ReplicationStatus
*/
public boolean isPublished(Resource resource) {
ReplicationStatus status = resource.adaptTo(ReplicationStatus.class);
return status != null && status.isActivated();
}
}
Use ReplicationStatusProvider.getBatchReplicationStatus() when you need to check the publication state of multiple resources in a single call. It is more efficient than calling Replicator.getReplicationStatus() in a loop.
Javadoc: com.day.cq.replication.ReplicationStatusProvider
import com.day.cq.replication.ReplicationStatus;
import com.day.cq.replication.ReplicationStatusProvider;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(service = BatchStatusChecker.class)
public class BatchStatusChecker {
@Reference
private ReplicationStatusProvider replicationStatusProvider;
/**
* Returns a map of path → ReplicationStatus for all given paths.
* Uses ReplicationStatusProvider.getBatchReplicationStatus(Resource...)
* which is more efficient than per-path Replicator.getReplicationStatus() calls.
*/
public Map<String, Boolean> checkPublishedState(ResourceResolver resolver, String[] paths) {
// Resolve all paths to Resource objects
Resource[] resources = Arrays.stream(paths)
.map(resolver::getResource)
.filter(Objects::nonNull)
.toArray(Resource[]::new);
// Single batch call — returns Map<path, ReplicationStatus>
Map<String, ReplicationStatus> statusMap =
replicationStatusProvider.getBatchReplicationStatus(resources);
Map<String, Boolean> result = new LinkedHashMap<>();
for (String path : paths) {
ReplicationStatus status = statusMap.get(path);
result.put(path, status != null && status.isActivated());
}
return result;
}
}
Required imports:
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
public class PermissionValidator {
@Reference
private Replicator replicator;
/**
* Check if user has permission to replicate
*/
public boolean canUserReplicate(Session session, String path) {
try {
replicator.checkPermission(
session,
ReplicationActionType.ACTIVATE,
path
);
return true;
} catch (ReplicationException e) {
// User lacks permission
return false;
}
}
/**
* Validate before replicating
*/
public void safeReplicate(Session session, String path)
throws ReplicationException {
// Check permission first
replicator.checkPermission(session, ReplicationActionType.ACTIVATE, path);
// Proceed with replication
replicator.replicate(session, ReplicationActionType.ACTIVATE, path);
}
}
import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.WorkItem;
import com.adobe.granite.workflow.exec.WorkflowProcess;
import com.adobe.granite.workflow.metadata.MetaDataMap;
import org.osgi.service.component.annotations.Component;
@Component(
service = WorkflowProcess.class,
property = {
"process.label=Publish Content to Publish Tier"
}
)
public class PublishWorkflowProcess implements WorkflowProcess {
@Reference
private Replicator replicator;
@Override
public void execute(WorkItem workItem, WorkflowSession workflowSession,
MetaDataMap args) throws WorkflowException {
String payloadPath = workItem.getWorkflowData().getPayload().toString();
try {
Session session = workflowSession.adaptTo(Session.class);
replicator.replicate(
session,
ReplicationActionType.ACTIVATE,
payloadPath
);
} catch (ReplicationException e) {
throw new WorkflowException("Failed to publish content", e);
}
}
}
Process Arguments (configured in workflow model):
MetaDataMap args to pass custom parameters like target tierUse OSGi Event Handlers to react to replication events:
import org.osgi.service.component.annotations.Component;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(
service = EventHandler.class,
property = {
org.osgi.service.event.EventConstants.EVENT_TOPIC + "=" + "com/day/cq/replication"
}
)
public class ReplicationEventLogger implements EventHandler {
private static final Logger LOG = LoggerFactory.getLogger(ReplicationEventLogger.class);
@Override
public void handleEvent(Event event) {
String[] paths = (String[]) event.getProperty("paths");
String action = (String) event.getProperty("action");
String userId = (String) event.getProperty("userId");
if (paths != null) {
for (String path : paths) {
LOG.info("Replication: action={}, path={}, user={}", action, path, userId);
}
}
}
}
Event Properties:
paths - Array of replicated pathsaction - Replication action (Activate, Deactivate, etc.)userId - User who triggered replicationDon't build custom bulk publishing code. Use Tree Activation workflow step.
Always check permissions before replication to avoid exceptions.
Never replicate with admin credentials. Map a sub-service name to the principal that holds crx:replicate via ServiceUserMapperImpl.amended config. Provision the principal with jcr:read + crx:replicate on /content via Repo Init scripts — see AEM Project Structure — Repo Init. See references/patterns.md for the full service user setup example.
Minimize replication volume to reduce queue pressure and pipeline load.
See references/patterns.md for complete implementations:
SlingConstants.TOPIC_RESOURCE_CHANGEDCheck: Verify replication queues in Felix console (Sling Jobs console):
https://author-p<programId>-e<envId>.adobeaemcloud.com/system/console/slingjobs
Look for failed jobs with topic: com/day/cq/replication
Error: javax.jcr.AccessDeniedException
Solution: Verify service user has replication permissions:
/content/mysite -> jcr:read, crx:replicate
Check:
replicator.getReplicationStatus()