Home Services Blog → Adobe Analytics → Adobe Experience Platform → Adobe Target → AEM → Google Analytics 4 → Segment CDP About Contact
AEM

Migrating Oak Indexes from AEM 6.5 to AEM as a Cloud Service using Adobe's Index Converter (Step-by-Step)

Your AEM 6.5 indexes won't pass Cloud Manager validation without conversion. Here's the complete, hands-on guide to using Adobe's Index Converter—including the real errors you'll hit and how to fix them.

Why Oak Indexes Need Conversion

What Are Oak Indexes?

Oak indexes are the backbone of AEM's query performance. When you run a query like SELECT * FROM [cq:Page] WHERE [jcr:content/cq:template] = '/conf/mysite/templates/page', Oak uses indexes to avoid scanning the entire repository.

In AEM 6.5, you likely have custom Lucene indexes defined under /oak:index with properties like includedPaths, excludedPaths, custom analyzers, and aggregation rules. These were deployed via content packages or manually created in CRXDE.

Why AEM Cloud Has Stricter Rules

AEM as a Cloud Service fundamentally changes how indexes are managed:

  • Immutable repository — You can't create or modify indexes at runtime
  • Deployment validation — Cloud Manager rejects deprecated properties and unsupported configurations
  • Standardized structure — Indexes must be deployed as content packages with specific JCR paths
  • No custom analyzers — Many AEM 6.5 analyzer configurations aren't supported

If you push your AEM 6.5 indexes directly to Cloud Manager, the build will fail with cryptic validation errors.

What the Index Converter Does (and Doesn't Do)

✅ What It Does

Transforms index definitions to Cloud-compatible format. Removes deprecated properties. Restructures paths. Generates deployment-ready content packages.

⚠️ What It Does NOT Do

It won't fix your queries. It won't optimize your indexes. It won't tell you if your indexes are even being used. You still need to validate query performance after conversion.

Prerequisites

Required Tools

  • Node.js (v18+ recommended)
  • npm
  • Adobe aio-cli (Adobe I/O CLI)
  • aem-cloud-migration plugin

Install Adobe aio-cli

# Install Adobe I/O CLI globally
npm install -g @adobe/aio-cli

# Verify installation
aio --version

Install the AEM Cloud Migration Plugin

# Install the migration plugin
aio plugins:install @adobe/aio-cli-plugin-aem-cloud-migration

# Verify it's installed
aio plugins

You should see @adobe/aio-cli-plugin-aem-cloud-migration in the list.

Confirm Plugin Commands Are Available

# List available commands
aio aem-cloud-migration --help

Look for index-converter in the command list.

⚠️ Gotcha: Global aio Config vs Index Converter Config

The global aio-cli config file (~/.config/@adobe/aio-cli/config.yaml) is NOT the same as the index converter config. The converter uses its own YAML file that you create specifically for the conversion. Don't confuse the two.

Demo Setup Using WKND

We'll use the WKND sample project. It's better maintained than We.Retail and mirrors real-world AEM project structures.

Clone and Build WKND

# Clone WKND
git clone https://github.com/adobe/aem-guides-wknd.git
cd aem-guides-wknd

# Build (skip tests for speed)
mvn clean install -DskipTests

Create a Custom Index Folder

WKND doesn't have custom indexes by default. Let's create a sample to demonstrate the conversion process.

# Create the _oak_index folder structure
mkdir -p ui.apps/src/main/content/jcr_root/_oak_index/wknd-content-index

Add a Sample Index Definition

Create the .content.xml file for your index:

ui.apps/src/main/content/jcr_root/_oak_index/wknd-content-index/.content.xml
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
          xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
          xmlns:oak="http://jackrabbit.apache.org/oak/ns/1.0"
    jcr:primaryType="oak:QueryIndexDefinition"
    type="lucene"
    async="async"
    compatVersion="{Long}2"
    includedPaths="[/content/wknd]"
    queryPaths="[/content/wknd]"
    evaluatePathRestrictions="{Boolean}true">
    <indexRules jcr:primaryType="nt:unstructured">
        <nt:unstructured jcr:primaryType="nt:unstructured">
            <properties jcr:primaryType="nt:unstructured">
                <jcrTitle
                    jcr:primaryType="nt:unstructured"
                    name="jcr:content/jcr:title"
                    propertyIndex="{Boolean}true"/>
                <cqTemplate
                    jcr:primaryType="nt:unstructured"
                    name="jcr:content/cq:template"
                    propertyIndex="{Boolean}true"/>
            </properties>
        </nt:unstructured>
    </indexRules>
</jcr:root>

File Structure After Setup

aem-guides-wknd/
├── ui.apps/
│   └── src/
│       └── main/
│           └── content/
│               ├── jcr_root/
│               │   └── _oak_index/
│               │       └── wknd-content-index/
│               │           └── .content.xml
│               └── META-INF/
│                   └── vault/
│                       └── filter.xml

Update filter.xml to Include Index Path

Add the index path to your filter.xml:

ui.apps/src/main/content/META-INF/vault/filter.xml
<?xml version="1.0" encoding="UTF-8"?>
<workspaceFilter version="1.0">
    <!-- Existing filters -->
    <filter root="/apps/wknd"/>
    
    <!-- Add this line for oak indexes -->
    <filter root="/_oak_index/wknd-content-index"/>
</workspaceFilter>

Create Index Converter Config YAML

The Index Converter needs a configuration file telling it where to find your indexes and filter.xml.

Full Config File

Create index-converter.yaml in your project root:

index-converter.yaml
# AEM version of your source instance
aemVersion: "6.5"

# Path to your custom oak index definitions
# This is where your _oak_index folder lives
customOakIndexDirectoryPath: "/Users/yourname/aem-guides-wknd/ui.apps/src/main/content/jcr_root/_oak_index"

# Path to filter.xml (must be a FILE, not directory)
filterXMLPath: "/Users/yourname/aem-guides-wknd/ui.apps/src/main/content/META-INF/vault/filter.xml"

# (Optional) Where to output the converted index definitions
# If not set, converter creates folders in current directory
indexConversionDefinitionContentPackageJcrRootPath: "/Users/yourname/aem-guides-wknd/ui.apps/src/main/content/jcr_root"

# (Optional) Use the same path for config package output
useIndexDefinitionConfigPackageJcrRootPath: "/Users/yourname/aem-guides-wknd/ui.apps/src/main/content/jcr_root"

Config Field Reference

Field What It Does
aemVersion Source AEM version. Use "6.5" for AEM 6.5.x. Do NOT use "6.4" unless you're actually on 6.4.
customOakIndexDirectoryPath Absolute path to your _oak_index or oak:index folder containing index definitions.
filterXMLPath Absolute path to filter.xml FILE (not directory). Must end with filter.xml.
indexConversionDefinitionContentPackageJcrRootPath Where to write the converted index definition content package.
useIndexDefinitionConfigPackageJcrRootPath Where to write the converted index config package.

How to Find the Right Paths in Your Repo

Locate filter.xml

# Find all filter.xml files in your project
find . -name "filter.xml" -type f

# Typical locations:
# ./ui.apps/src/main/content/META-INF/vault/filter.xml
# ./ui.content/src/main/content/META-INF/vault/filter.xml

Locate Oak Index Content

# Find _oak_index folders
find . -type d -name "_oak_index"

# Find oak:index folders (may be named differently)
find . -type d -name "oak*index*"

# Search for index definitions by content
grep -r "oak:QueryIndexDefinition" --include="*.xml" .

Verify filter.xml Includes Index Paths

# Check what paths are in filter.xml
cat ui.apps/src/main/content/META-INF/vault/filter.xml | grep "filter root"

Make sure your _oak_index path is included. If not, add a filter entry for it.

Run the Index Converter

Correct Command Syntax

⚠️ Important: Use Space-Separated Format

The correct command uses spaces, not colons. The old colon-style (index-converter:convert) is deprecated and may not work.

# Navigate to your project root
cd /Users/yourname/aem-guides-wknd

# Run the converter
aio aem-cloud-migration index-converter --config ./index-converter.yaml

Expected Output

Index Converter starting...
Reading config from ./index-converter.yaml
AEM Version: 6.5
Processing custom index definitions from: /Users/yourname/aem-guides-wknd/ui.apps/src/main/content/jcr_root/_oak_index

Converting index: wknd-content-index
  - Removed deprecated property: compatVersion
  - Updated index structure for Cloud compatibility
  
Writing converted indexes to: /Users/yourname/aem-guides-wknd/ui.apps/src/main/content/jcr_root

Conversion complete.
  Indexes converted: 1
  Warnings: 0
  Errors: 0

Where Results Are Written

The converter creates or modifies files in your jcr_root directory. Look for:

  • _oak_index/ — Updated index definitions
  • New .content.xml files with Cloud-compatible properties
  • Removed deprecated nodes and properties

Validate the Results

What Files/Folders Changed

# See what the converter modified
git status

# Diff a specific index
git diff ui.apps/src/main/content/jcr_root/_oak_index/wknd-content-index/.content.xml

Common Warnings and What They Mean

Warning Meaning
Unsupported analyzer Custom Lucene analyzer not supported in Cloud. Will use default analyzer.
Deprecated property removed Property like compatVersion was removed as it's not used in Cloud.
Index path restructured Index moved to Cloud-compatible path structure.
Aggregate rule modified Aggregation configuration adjusted for Cloud compatibility.

What to Check Into Git

  • Updated .content.xml files in _oak_index
  • Any new folders created by the converter
  • Updated filter.xml if paths changed
  • Do NOT check in the converter config file (it has local paths)

How to Re-Run Safely

The converter is idempotent—you can run it multiple times:

# Re-run after making changes
aio aem-cloud-migration index-converter --config ./index-converter.yaml

# If you need a fresh start, reset your changes first
git checkout -- ui.apps/src/main/content/jcr_root/_oak_index/

Troubleshooting

Error A: "index-converter:convert is not a aio command"

$ aio index-converter:convert --config ./config.yaml
index-converter:convert is not a aio command.
Did you mean aem-cloud-migration index-converter?

Fix: Use the space-separated command format:

# Wrong (old/deprecated)
aio index-converter:convert --config ./config.yaml

# Correct
aio aem-cloud-migration index-converter --config ./config.yaml

Error B: "Unable to read baseline.xml for AEM Version 6.4"

Error: Use AEM Version for Reference 6.4
Unable to read baseline.xml for AEM Version 6.4

Fix: Set the correct AEM version in your config:

# In your index-converter.yaml
aemVersion: "6.5"    # NOT "6.4" unless you're actually on 6.4

The converter uses baseline definitions for each AEM version. If you specify the wrong version, it can't find the reference files.

Error C: "cat /home/develop/.config/@adobe/aio-cli/config.yaml: No such file"

Error: cat /home/develop/.config/@adobe/aio-cli/config.yaml: No such file or directory

Fix: This is a HOME directory issue. The error is misleading—it's looking for the global aio config, not your converter config.

# Check your HOME is set correctly
echo $HOME

# The global aio config location varies:
# macOS: ~/.config/@adobe/aio-cli/
# Linux: ~/.config/@adobe/aio-cli/

# This is NOT your index-converter.yaml
# Your converter config is a separate file you create

Make sure you're passing the correct path to YOUR converter config file, not the aio global config.

Error D: "cd .../META-INF/vault/filter.xml: Not a directory"

Error: cd /Users/yourname/project/ui.apps/src/main/content/META-INF/vault/filter.xml: Not a directory

Fix: The filterXMLPath must be a FILE path, not a directory:

# Wrong - pointing to directory
filterXMLPath: "/path/to/META-INF/vault/"

# Correct - pointing to the file
filterXMLPath: "/path/to/META-INF/vault/filter.xml"

This error can also occur with older plugin versions. Try upgrading:

aio plugins:update
aio plugins:install @adobe/aio-cli-plugin-aem-cloud-migration@latest

General Fix: Upgrade the Plugin

Many issues are resolved by updating to the latest plugin version:

# Update all plugins
aio plugins:update

# Or reinstall the specific plugin
aio plugins:uninstall @adobe/aio-cli-plugin-aem-cloud-migration
aio plugins:install @adobe/aio-cli-plugin-aem-cloud-migration@latest

# Verify version
aio plugins

Next Steps

Validate Queries with Oak Tools

After conversion, verify your queries actually use the indexes. In AEM:

  • Go to Tools → Operations → Diagnosis → Query Performance
  • Run your queries and check the "Index Used" column
  • Queries showing "traversal" need index optimization

Deploy via Cloud Manager

  1. Commit your converted indexes to Git
  2. Push to your Cloud Manager repository
  3. Run a build pipeline—it should pass validation now
  4. Deploy to dev environment first

Coordinate with Performance Testing

  • Run load tests after deploying new indexes
  • Monitor index reindexing time (can take hours for large repositories)
  • Watch for query performance regressions

Multi-Repo Architecture: Centralize Index Ownership

If you have multiple repositories feeding into an aggregator:

  • Keep all index definitions in ONE repository (usually the aggregator)
  • Don't let individual tenant repos define their own indexes
  • Use naming conventions to identify index ownership
  • Document which indexes support which queries
✅ Pro Tip

Create a README.md in your _oak_index folder documenting each index: what queries it supports, who owns it, and when it was last modified. Future you will thank you.

Related Topics

Oak index converter AEM index migration AEM Cloud indexes Lucene index AEM aio-cli AEM Cloud Manager validation AEM 6.5 migration oak:index AEM query performance WegWorks AEM consulting

Need Help with AEM Cloud Migration?

Index conversion is just one piece of the puzzle. Our team has migrated enterprise AEM implementations to the cloud—let us help you avoid the pitfalls.

Get Expert Help