Purge Old CRX Packages AEM

Problem statement:

AEM environment size is increasing because of user-generated packages


Can we purge all the user-generated packages to improve stability?


A package is a zip file holding repository content in the form of a file-system serialization (called “vault” serialization). This provides an easy-to-use-and-edit representation of files and folders.

Packages include content, both page content and project-related content, selected using filters.

A package also contains vault meta information, including the filter definitions and import configuration information. Additional content properties (that are not used for package extraction) can be included in the package, such as a description, a visual image, or an icon; these properties are for the content package consumer and for informational purposes only.

Usually, Developers or AEM content synch or even code deployment will keep on piling up in the CRX packages and it will be consuming spaces on MBs and even sometimes GBs.

If we move more packages, then loading crx/packmgr would more time.

Hence you can create a scheduler that runs on off-hours which cleans up the packages and which will get back the space and avoids extra maintenance tasks.

The below scheduler will remove all the packages for my_package group we can add business logic to handle for other groups

package com.mysite.core.schedulers;

import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

@ObjectClassDefinition(name = "Old Packages Purge Schedular", description = "Remove old packages from different paths")
public @interface PurgeOldPackagesSchedulerConfig {

    String DEFAULT_SCHEDULER_EXPRESSION = "0 0 16 ? * SUN *"; // every Sunday 4 PM

    @AttributeDefinition(name = "Enabled", description = "True, if scheduler service is enabled", type = AttributeType.BOOLEAN)
    boolean enabled() default true;

    @AttributeDefinition(name = "Cron expression defining when this Scheduled Service will run", description = "[every minute = 0 * * * * ?], [12:00am daily = 0 0 0 ? * *]", type = AttributeType.STRING)
    String schedulerExpression() default DEFAULT_SCHEDULER_EXPRESSION;

    @AttributeDefinition(name = "package paths", description = "package folder paths", type = AttributeType.STRING)
    String[] packagesPaths() default {"my_packages"};
package com.mysite.core.schedulers;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.apache.jackrabbit.vault.packaging.JcrPackage;
import org.apache.jackrabbit.vault.packaging.JcrPackageManager;
import org.apache.jackrabbit.vault.packaging.Packaging;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.commons.scheduler.ScheduleOptions;
import org.apache.sling.commons.scheduler.Scheduler;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(immediate = true, service = PurgeOldPackagesScheduler.class, configurationPolicy = ConfigurationPolicy.REQUIRE)
@Designate(ocd = PurgeOldPackagesSchedulerConfig.class)
public class PurgeOldPackagesScheduler implements Runnable {

    private static final Logger log = LoggerFactory.getLogger(PurgeOldPackagesScheduler.class);
     * Id of the scheduler based on its name
    private String schedulerJobName;

    Session session;
    private List<String> packagesPathsList;

    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

    private Packaging packaging;

    private Scheduler scheduler;

    private ResourceResolverFactory resolverFactory;

    protected void activate(PurgeOldPackagesSchedulerConfig purgeOldPackagesSchedulerConfig) {
         * Creating the scheduler id

        this.schedulerJobName = this.getClass().getSimpleName();
        packagesPathsList = Arrays.asList(purgeOldPackagesSchedulerConfig.packagesPaths());

     * @see Runnable#run().
    public final void run() {
        log.debug("PurgeOldPackagesScheduler Job started");
        log.debug("PurgeOldPackagesScheduler Job completed");

    void purgeOldPackages(List<String> packagesPathsList) {
        try (ResourceResolver resourceResolver = resolverFactory.getServiceResourceResolver(Collections
                .singletonMap(ResourceResolverFactory.SUBSERVICE, ServiceUserConstants.ADMINISTRATIVE_SERVICE_USER))) {
            session = resourceResolver.adaptTo(Session.class);
            JcrPackageManager jcrPackageManager = packaging.getPackageManager(session);
            packagesPathsList.forEach(group -> {
                removePackages(jcrPackageManager, 0, group);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (session != null) {

    protected void deactivate() {

         * Removing the scheduler

     * This method adds the scheduler
     * @param purgeOldPackagesSchedulerConfig
    private void addScheduler(PurgeOldPackagesSchedulerConfig purgeOldPackagesSchedulerConfig) {

         * Check if the scheduler is enabled
        if (purgeOldPackagesSchedulerConfig.enabled()) {

             * Scheduler option takes the cron expression as a parameter and run accordingly
            ScheduleOptions scheduleOptions = scheduler.EXPR(purgeOldPackagesSchedulerConfig.schedulerExpression());

             * Adding some parameters

             * Scheduling the job
            scheduler.schedule(this, scheduleOptions);

            log.info("{} Scheduler added", schedulerJobName);
        } else {
            log.info("Scheduler is disabled");


     * This method removes the scheduler
    private void removeScheduler() {

        log.info("Removing scheduler: {}", schedulerJobName);

         * Unscheduling/removing the scheduler

    private void removePackages(JcrPackageManager jcrPackageManager, int counterStart, String groupName) {
        try {
            List<JcrPackage> packages = jcrPackageManager.listPackages(groupName, false);
            AtomicInteger counter = new AtomicInteger(counterStart);
            if (null != packages && !packages.isEmpty()) {
                packages.stream().sorted(Comparator.nullsLast((e1, e2) -> {
                    try {
                        return ((JcrPackage) e1).getPackage().getCreated()
                                .compareTo(((JcrPackage) e2).getPackage().getCreated());
                    } catch (RepositoryException | IOException ex) {
                    return -1;
                }).reversed()).forEach(pack -> {
                    if (counter.incrementAndGet() > 3) {
                        try {
                        } catch (RepositoryException ex) {
        } catch (RepositoryException e) {

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s