<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://drfits.com/feed.xml" rel="self" type="application/atom+xml"/><link href="https://drfits.com/" rel="alternate" type="text/html"/><updated>2026-05-01T11:39:38+00:00</updated><id>https://drfits.com/feed.xml</id><title type="html">DevLog</title><subtitle>Thoughts on code, systems, and the craft of engineering.</subtitle><author><name>Evgeniy Fitsner</name><email>drfits@drfits.com</email></author><entry><title type="html">Grounded Docs MCP Server: Local RAG Documentation for AI Agents</title><link href="https://drfits.com/2026/05/01/docs-mcp-server-rag-for-ai-agents/" rel="alternate" type="text/html" title="Grounded Docs MCP Server: Local RAG Documentation for AI Agents"/><published>2026-05-01T00:00:00+00:00</published><updated>2026-05-01T00:00:00+00:00</updated><id>https://drfits.com/2026/05/01/docs-mcp-server-rag-for-ai-agents</id><content type="html" xml:base="https://drfits.com/2026/05/01/docs-mcp-server-rag-for-ai-agents/"><![CDATA[<p>AI coding assistants are only as good as the context they can access. When a model answers questions about a library or framework, outdated training data leads to hallucinations and incorrect APIs. Grounded Docs MCP Server solves this by turning any documentation into a local, queryable RAG index that your AI agent can access through the Model Context Protocol.</p> <h2 id="what-is-grounded-docs-mcp-server">What Is Grounded Docs MCP Server</h2> <p><a href="https://grounded.tools/">Grounded Docs MCP Server</a> is a free, open-source documentation indexing tool that fetches and embeds docs from websites, GitHub repositories, npm, PyPI, and local files. It runs entirely on your machine, so no code or queries leave your network. The server exposes a standard MCP interface, making it compatible with OpenCode, IntelliJ IDEA, Claude, Cline, VS Code Copilot, Gemini CLI and any other MCP-compatible client.</p> <p>Key features include:</p> <ul> <li><strong>Cross-platform</strong> - works on Windows, macOS, and Linux</li> <li><strong>Free and open source</strong> - MIT license, self-hosted with no usage limits</li> <li><strong>Broad format support</strong> - HTML, Markdown, PDF, Office documents, source code, and more</li> <li><strong>Version-specific indexing</strong> - target the exact library version used in your project</li> <li><strong>Multiple sources</strong> - index websites, GitHub repos, local folders, and ZIP archives</li> </ul> <h2 id="prerequisites">Prerequisites</h2> <p>You need Node.js 22 or newer installed on your system. The server is distributed via npm and executed through <code class="language-plaintext highlighter-rouge">npx</code>, so no global installation is required.</p> <p>If you plan to use local embeddings instead of a cloud provider, you also need <a href="https://ollama.com/">Ollama</a> installed. This guide configures the server with the <code class="language-plaintext highlighter-rouge">bge-m3</code> embedding model through Ollama for a fully offline setup.</p> <h2 id="install-ollama-and-pull-the-embedding-model">Install Ollama and Pull the Embedding Model</h2> <p>The server supports several embedding providers, including OpenAI, Gemini, Azure, and Ollama. The default cloud model is OpenAI’s <code class="language-plaintext highlighter-rouge">text-embedding-3-small</code>, which works well for English-only content. For stronger multilingual support and better accuracy across programming docs in different languages, this guide uses <code class="language-plaintext highlighter-rouge">bge-m3</code> running locally through Ollama.</p> <p>Install Ollama for your platform, then pull the model:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>ollama pull bge-m3
</pre></td></tr></tbody></table></code></pre></div></div> <p>Once the download finishes, Ollama will serve the model on its default port. The Docs MCP Server will communicate with it automatically when configured.</p> <h2 id="configure-the-server">Configure the Server</h2> <p>Grounded Docs MCP Server stores its settings in a local config file. You can tune the embedding model, index storage path, and scraper behavior through the CLI.</p> <p>Run the following commands to apply the configuration used in this guide:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre>npx @arabold/docs-mcp-server@latest config <span class="nb">set </span>app.storePath /Users/drfits/develop/mcp_servers/documentation_store <span class="o">&amp;&amp;</span> <span class="se">\</span>
npx @arabold/docs-mcp-server@latest config <span class="nb">set </span>app.embeddingModel bge-m3 <span class="o">&amp;&amp;</span> <span class="se">\</span>
npx @arabold/docs-mcp-server@latest config <span class="nb">set </span>scraper.maxPages 100000 <span class="o">&amp;&amp;</span> <span class="se">\</span>
npx @arabold/docs-mcp-server@latest config <span class="nb">set </span>scraper.maxDepth 5 <span class="o">&amp;&amp;</span> <span class="se">\</span>
npx @arabold/docs-mcp-server@latest config <span class="nb">set </span>scraper.maxConcurrency 3 <span class="o">&amp;&amp;</span> <span class="se">\</span>
npx @arabold/docs-mcp-server@latest config <span class="nb">set </span>splitter.preferredChunkSize 1000 <span class="o">&amp;&amp;</span> <span class="se">\</span>
npx @arabold/docs-mcp-server@latest config <span class="nb">set </span>splitter.minChunkSize 300 <span class="o">&amp;&amp;</span> <span class="se">\</span>
npx @arabold/docs-mcp-server@latest config <span class="nb">set </span>splitter.maxChunkSize 3000
</pre></td></tr></tbody></table></code></pre></div></div> <p>Explanation of each setting:</p> <ul> <li><code class="language-plaintext highlighter-rouge">app.storePath</code> - directory where the vector index and metadata are stored</li> <li><code class="language-plaintext highlighter-rouge">app.embeddingModel</code> - first resets the model, then sets it to <code class="language-plaintext highlighter-rouge">bge-m3</code></li> <li><code class="language-plaintext highlighter-rouge">scraper.maxPages</code> - maximum number of pages to fetch during a scrape job</li> <li><code class="language-plaintext highlighter-rouge">scraper.maxDepth</code> - how many link levels to follow from the starting URL</li> <li><code class="language-plaintext highlighter-rouge">scraper.maxConcurrency</code> - number of parallel fetch requests</li> <li><code class="language-plaintext highlighter-rouge">splitter.preferredChunkSize</code> - target size for each text chunk; smaller chunks return more precise results with less noise</li> <li><code class="language-plaintext highlighter-rouge">splitter.minChunkSize</code> - minimum chunk size to avoid indexing useless fragments like single-word headings</li> <li><code class="language-plaintext highlighter-rouge">splitter.maxChunkSize</code> - upper limit for cases where an unbreakable block of text is encountered</li> </ul> <p>A <code class="language-plaintext highlighter-rouge">preferredChunkSize</code> of 1000 creates more granular fragments. For code this is critical: it is better to retrieve three small, precise functions than one massive wall of text. Adjust <code class="language-plaintext highlighter-rouge">storePath</code> and concurrency to match your hardware and project size.</p> <h2 id="running-the-server-locally-via-cli">Running the Server Locally via CLI</h2> <p>You can start the Docs MCP Server directly from the command line, routing embedding requests through Ollama. This is useful when you want full control over the launch process or need to debug the server output.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="nb">export </span><span class="nv">OPENAI_API_KEY</span><span class="o">=</span><span class="s2">"ollama"</span>
<span class="nb">export </span><span class="nv">OPENAI_API_BASE</span><span class="o">=</span><span class="s2">"http://localhost:11434/v1"</span>

npx @arabold/docs-mcp-server@latest server <span class="se">\</span>
  <span class="nt">--store-path</span> /Users/drfits/develop/mcp_servers/documentation_store <span class="se">\</span>
  <span class="nt">--embedding-model</span> <span class="s2">"openai:bge-m3"</span>
</pre></td></tr></tbody></table></code></pre></div></div> <p>The <code class="language-plaintext highlighter-rouge">openai:</code> prefix tells the embedding library to send requests in the standard OpenAI API format, which Ollama understands natively. The <code class="language-plaintext highlighter-rouge">OPENAI_API_KEY</code> value is arbitrary here since Ollama does not require authentication; setting it to <code class="language-plaintext highlighter-rouge">"ollama"</code> is a common convention.</p> <p>Once the server starts, the Web UI is available at <code class="language-plaintext highlighter-rouge">http://localhost:6280</code> and the SSE endpoint at <code class="language-plaintext highlighter-rouge">http://localhost:6280/sse</code>.</p> <h2 id="platform-specific-mcp-client-configuration">Platform-Specific MCP Client Configuration</h2> <p>After starting the server with <code class="language-plaintext highlighter-rouge">npx @arabold/docs-mcp-server@latest</code>, the Web UI becomes available at <code class="language-plaintext highlighter-rouge">http://localhost:6280</code>. You can add documentation sources there, or use the CLI to scrape directly.</p> <p>To connect your AI client, add an MCP server entry pointing to the local SSE endpoint. The JSON structure is identical across all platforms:</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"mcpServers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"docs-mcp-server"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sse"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://localhost:6280/sse"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div> <p>Where you place this configuration depends on your client and operating system.</p> <h3 id="opencode">OpenCode</h3> <p>Create or open <code class="language-plaintext highlighter-rouge">opencode.json</code> in your project root and add the MCP server entry under the <code class="language-plaintext highlighter-rouge">mcp</code> key:</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"$schema"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://opencode.ai/config.json"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"mcp"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"docs-mcp-server"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sse"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://localhost:6280/sse"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div> <p>This works the same way on macOS, Windows, and Linux because the file lives inside your project.</p> <h2 id="choosing-an-embedding-model">Choosing an Embedding Model</h2> <p>The server supports multiple embedding strategies. The table below compares the two most common options:</p> <table> <thead> <tr> <th>Model</th> <th>Provider</th> <th>Strengths</th> <th>Best For</th> </tr> </thead> <tbody> <tr> <td><code class="language-plaintext highlighter-rouge">text-embedding-3-small</code></td> <td>OpenAI (cloud)</td> <td>Fast, low cost, easy setup</td> <td>English-only documentation, quick experiments</td> </tr> <tr> <td><code class="language-plaintext highlighter-rouge">bge-m3</code></td> <td>Ollama (local)</td> <td>Multilingual, high accuracy, fully offline</td> <td>Teams with non-English docs, air-gapped environments</td> </tr> </tbody> </table> <p>If your documentation includes languages other than English, or if you want to avoid sending data to third-party APIs, <code class="language-plaintext highlighter-rouge">bge-m3</code> through Ollama is the better choice. It requires more local resources but keeps everything private.</p> <h2 id="sharing-indexed-documentation-across-your-team">Sharing Indexed Documentation Across Your Team</h2> <p>Once a documentation index is built, you can share it with teammates without requiring everyone to scrape the same sources again. This is especially useful for large frameworks or internal documentation portals.</p> <p>Set the server to read-only mode so that team members can query the index but cannot accidentally modify or reindex it:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nv">DOCS_MCP_READ_ONLY</span><span class="o">=</span><span class="nb">true </span><span class="nv">DOCS_MCP_STORAGE_PATH</span><span class="o">=</span>/Users/drfits/develop/mcp_servers/documentation_store npx @arabold/docs-mcp-server@latest
</pre></td></tr></tbody></table></code></pre></div></div> <p>In this mode, the server loads the existing index from <code class="language-plaintext highlighter-rouge">DOCS_MCP_STORAGE_PATH</code> and disables all write operations. Use the same path you configured earlier with <code class="language-plaintext highlighter-rouge">app.storePath</code>.</p> <p>To distribute the index, place the store directory on a shared cloud drive. Any service that syncs a local folder works:</p> <ul> <li><strong>OneDrive</strong></li> <li><strong>Google Drive</strong></li> <li><strong>Yandex Disk</strong></li> </ul> <p>One team member performs the initial indexing with write access. After the first sync completes, everyone else points their <code class="language-plaintext highlighter-rouge">app.storePath</code> to the synced folder and launches the server in read-only mode. This gives the entire team a consistent, up-to-date documentation source without duplicate network traffic or API costs.</p> <h2 id="conclusion">Conclusion</h2> <p>Grounded Docs MCP Server brings local RAG to any AI coding assistant. By indexing documentation on your own hardware and exposing it through MCP, you eliminate outdated knowledge and keep your agent grounded in the exact APIs and versions you use. The cross-platform support, free open-source license, and ability to share indexes across teams make it a practical addition to any development workflow.</p> <p>Start with Ollama and <code class="language-plaintext highlighter-rouge">bge-m3</code> for a fully offline setup, or use OpenAI embeddings for a lighter cloud-backed configuration. Either way, your AI agent will finally have documentation it can trust.</p>]]></content><author><name>Evgeniy Fitsner</name><email>drfits@drfits.com</email></author><category term="ai"/><category term="mcp"/><summary type="html"><![CDATA[Set up Grounded Docs MCP Server as a local RAG documentation source for AI agents using Ollama embeddings and team-shared indexes.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://drfits.com/assets/images/posts/2026-05-01-docs-mcp-server-rag-for-ai-agents/cover.webp"/><media:content medium="image" url="https://drfits.com/assets/images/posts/2026-05-01-docs-mcp-server-rag-for-ai-agents/cover.webp" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">IntelliJ IDEA MCP Server Configuration for OpenCode</title><link href="https://drfits.com/2026/04/20/intellij-idea-mcp-server-configuration-for-opencode/" rel="alternate" type="text/html" title="IntelliJ IDEA MCP Server Configuration for OpenCode"/><published>2026-04-20T00:00:00+00:00</published><updated>2026-04-20T00:00:00+00:00</updated><id>https://drfits.com/2026/04/20/intellij-idea-mcp-server-configuration-for-opencode</id><content type="html" xml:base="https://drfits.com/2026/04/20/intellij-idea-mcp-server-configuration-for-opencode/"><![CDATA[<p>IntelliJ IDEA includes a built-in MCP (Model Context Protocol) server that allows AI tools like OpenCode to interact with your IDE. This guide walks you through enabling and configuring the MCP server, then connecting it to OpenCode at the project level.</p> <h2 id="step-1-enable-the-mcp-plugin">Step 1: Enable the MCP Plugin</h2> <p>Open IntelliJ IDEA settings and navigate to <strong>Settings -&gt; Plugins</strong>. Search for “MCP” and verify that the MCP plugin is installed and enabled. If it is not enabled, check the box and click <strong>Apply</strong>. You may need to restart the IDE for the changes to take effect.</p> <p><img src="/assets/images/posts/2026-04-20-intellij-idea-mcp-server-configuration-for-opencode/image-1.webp" alt="MCP Plugin in IntelliJ IDEA"/></p> <h2 id="step-2-enable-the-mcp-server">Step 2: Enable the MCP Server</h2> <p>Once the plugin is active, go to <strong>Settings -&gt; Tools -&gt; MCP Server</strong>. Check the <strong>Enable MCP Server</strong> option to turn on the server.</p> <p><img src="/assets/images/posts/2026-04-20-intellij-idea-mcp-server-configuration-for-opencode/image-2.webp" alt="MCP Server settings in IntelliJ IDEA"/></p> <h2 id="step-3-confirm-activation">Step 3: Confirm Activation</h2> <p>A confirmation dialog will appear. Click <strong>Enable</strong> to proceed.</p> <p><img src="/assets/images/posts/2026-04-20-intellij-idea-mcp-server-configuration-for-opencode/image-3.webp" alt="MCP Server activation confirmation"/></p> <h2 id="step-4-copy-the-sse-configuration">Step 4: Copy the SSE Configuration</h2> <p>After enabling the server, you will see the MCP configuration view. Click <strong>Copy SSE Config</strong> to copy the connection details to your clipboard. You will need this information when configuring OpenCode. Click <strong>OK</strong> to save the MCP server settings.</p> <p><img src="/assets/images/posts/2026-04-20-intellij-idea-mcp-server-configuration-for-opencode/image-4.webp" alt="MCP Server configuration view with Copy SSE Config button"/></p> <p>The SSE configuration follows this structure:</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sse"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://127.0.0.1:64342/sse"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"headers"</span><span class="p">:</span><span class="w"> </span><span class="p">{}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div> <h2 id="step-5-configure-opencode">Step 5: Configure OpenCode</h2> <p>Create or open the <code class="language-plaintext highlighter-rouge">opencode.json</code> file at the root of your project. Paste the SSE configuration under the <code class="language-plaintext highlighter-rouge">mcp</code> key, but change the <code class="language-plaintext highlighter-rouge">type</code> from <code class="language-plaintext highlighter-rouge">"sse"</code> to <code class="language-plaintext highlighter-rouge">"remote"</code>. This tells OpenCode to connect to the IntelliJ MCP server over SSE using the remote transport type.</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"$schema"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://opencode.ai/config.json"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"mcp"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"idea"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"remote"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://127.0.0.1:64342/sse"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"headers"</span><span class="p">:</span><span class="w"> </span><span class="p">{}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div> <p>The key difference is the <code class="language-plaintext highlighter-rouge">type</code> field - OpenCode uses <code class="language-plaintext highlighter-rouge">"remote"</code> for SSE endpoints, while IntelliJ exports the configuration with <code class="language-plaintext highlighter-rouge">"sse"</code>.</p> <h2 id="step-6-restart-and-verify">Step 6: Restart and Verify</h2> <p>Save the <code class="language-plaintext highlighter-rouge">opencode.json</code> file and restart your OpenCode client session. For the best results, restart IntelliJ IDEA as well to ensure the MCP server is fully initialized.</p> <p>Once both are running, OpenCode will connect to IntelliJ IDEA through the MCP server, giving you AI-powered coding assistance with full awareness of your project context, open files, and IDE state.</p>]]></content><author><name>Evgeniy Fitsner</name><email>drfits@drfits.com</email></author><category term="ai"/><category term="mcp"/><category term="intellij"/><summary type="html"><![CDATA[Step-by-step guide to configuring IntelliJ IDEA as an MCP server for OpenCode, enabling AI-powered coding assistance directly inside your IDE.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://drfits.com/assets/images/posts/2026-04-20-intellij-idea-mcp-server-configuration-for-opencode/cover.webp"/><media:content medium="image" url="https://drfits.com/assets/images/posts/2026-04-20-intellij-idea-mcp-server-configuration-for-opencode/cover.webp" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">AEM Dialog with extraClientlibs – Do Not Shoot Yourself in the Foot</title><link href="https://drfits.com/2021/02/11/aem-dialog-with-extraclientlibs-do-not-shot-yourself-in-the-foot/" rel="alternate" type="text/html" title="AEM Dialog with extraClientlibs – Do Not Shoot Yourself in the Foot"/><published>2021-02-11T00:00:00+00:00</published><updated>2021-02-11T00:00:00+00:00</updated><id>https://drfits.com/2021/02/11/aem-dialog-with-extraclientlibs-do-not-shot-yourself-in-the-foot</id><content type="html" xml:base="https://drfits.com/2021/02/11/aem-dialog-with-extraclientlibs-do-not-shot-yourself-in-the-foot/"><![CDATA[<p>AEM component dialogs let you attach custom JavaScript via the <code class="language-plaintext highlighter-rouge">extraClientlibs</code> property. It is a powerful feature - and a silent source of bugs. As your project accumulates components, each with its own dialog scripts, those scripts start colliding in ways that are hard to diagnose.</p> <p>This post explains how <code class="language-plaintext highlighter-rouge">extraClientlibs</code> work, why the conflicts happen, and three rules that keep your dialogs clean.</p> <h2 id="how-extraclientlibs-work">How extraClientlibs Work</h2> <p>When an author opens a component dialog, AEM retrieves the dialog’s XML definition and looks up the <code class="language-plaintext highlighter-rouge">extraClientlibs</code> property. Each category listed there is loaded as a client library - CSS and JS files are injected into the page on the spot.</p> <p>The key detail: <strong>dialog clientlib JS is never unloaded</strong>. Unlike page-level scripts that are replaced on navigation, dialog scripts remain on the page until the author reloads the editor. If an author opens dialog A, then closes it and opens dialog B, both A’s and B’s scripts are active at the same time.</p> <p>This is by design - AEM has no “dialog-closed” lifecycle event to hook into. But it means every dialog script you load accumulates in the page for the entire editing session.</p> <h2 id="the-problem-in-practice">The Problem in Practice</h2> <p>Consider a project with three components, each registering a <code class="language-plaintext highlighter-rouge">dialog-ready</code> listener:</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="c1">// Product Wizard - opens a multi-step wizard</span>
<span class="nf">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nf">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">dialog-ready</span><span class="dl">"</span><span class="p">,</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// bind wizstep navigation</span>
<span class="p">});</span>

<span class="c1">// Carousel - toggles slide visibility based on checkbox</span>
<span class="nf">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nf">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">dialog-ready</span><span class="dl">"</span><span class="p">,</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// show/hide slides</span>
<span class="p">});</span>

<span class="c1">// Teaser - auto-fills the CTA link from the image dam:link</span>
<span class="nf">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nf">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">dialog-ready</span><span class="dl">"</span><span class="p">,</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// resolve link from DAM metadata</span>
<span class="p">});</span>
</pre></td></tr></tbody></table></code></pre></div></div> <p>Each handler fires on <strong>every</strong> <code class="language-plaintext highlighter-rouge">dialog-ready</code> event, regardless of which component is being edited. As the project grows, the chance of one handler interfering with another increases - hidden fields get toggled, click handlers fire on the wrong form, validation runs against the wrong schema.</p> <p>The worst part: the bug only appears when a specific sequence of dialogs is opened, making it intermittent and hard to reproduce.</p> <h2 id="1-avoid-global-categories">1. Avoid Global Categories</h2> <p>The <code class="language-plaintext highlighter-rouge">cq.authoring.dialog</code> category is loaded on <strong>every</strong> dialog in AEM. Adding your component’s JS to it guarantees that your code runs in every dialog on the page, for every component, whether it needs to or not.</p> <p><strong>Anti-pattern:</strong></p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="c">&lt;!-- /apps/myproject/components/product/wizard/cq:dialog/.content.xml --&gt;</span>
<span class="nt">&lt;jcr:root</span> <span class="na">xmlns:cq=</span><span class="s">"http://www.day.com/jcr/cq/1.0"</span>
          <span class="na">xmlns:jcr=</span><span class="s">"http://www.jcp.org/jcr/1.0"</span>
          <span class="na">jcr:primaryType=</span><span class="s">"cq:Dialog"</span>
          <span class="na">extraClientlibs=</span><span class="s">"[cq.authoring.dialog]"</span><span class="nt">&gt;</span>
  <span class="c">&lt;!-- dialog fields --&gt;</span>
<span class="nt">&lt;/jcr:root&gt;</span>
</pre></td></tr></tbody></table></code></pre></div></div> <p>Reserve <code class="language-plaintext highlighter-rouge">cq.authoring.dialog</code> for truly global utilities - things like shared validation helpers or Coral UI polyfills that are safe across all dialogs. Component-specific logic does not belong here.</p> <h2 id="2-use-separate-descriptive-categories">2. Use Separate, Descriptive Categories</h2> <p>Create a dedicated clientlib category for each component (or logical group of components). Use a reverse-domain naming convention to avoid collisions:</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="c">&lt;!-- /apps/myproject/components/product/wizard/cq:dialog/.content.xml --&gt;</span>
<span class="nt">&lt;jcr:root</span> <span class="na">xmlns:cq=</span><span class="s">"http://www.day.com/jcr/cq/1.0"</span>
          <span class="na">xmlns:jcr=</span><span class="s">"http://www.jcp.org/jcr/1.0"</span>
          <span class="na">jcr:primaryType=</span><span class="s">"cq:Dialog"</span>
          <span class="na">extraClientlibs=</span><span class="s">"[myproject.components.product.wizard.authoring]"</span><span class="nt">&gt;</span>
  <span class="c">&lt;!-- dialog fields --&gt;</span>
<span class="nt">&lt;/jcr:root&gt;</span>
</pre></td></tr></tbody></table></code></pre></div></div> <p>The corresponding clientlib folder:</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="c">&lt;!-- /apps/myproject/clientlibs/product-wizard-authoring/.content.xml --&gt;</span>
<span class="nt">&lt;jcr:root</span> <span class="na">xmlns:jcr=</span><span class="s">"http://www.jcp.org/jcr/1.0"</span>
          <span class="na">jcr:primaryType=</span><span class="s">"cq:ClientLibraryFolder"</span>
          <span class="na">categories=</span><span class="s">"[myproject.components.product.wizard.authoring]"</span>
          <span class="na">dependencies=</span><span class="s">"[cq.authoring.dialogpage]"</span> <span class="nt">/&gt;</span>
</pre></td></tr></tbody></table></code></pre></div></div> <p><strong>Why this matters:</strong> category names are global. If two teams independently create a category called <code class="language-plaintext highlighter-rouge">product.dialog</code>, the second deployment overwrites the first. A long, path-based name guarantees uniqueness and makes the purpose self-documenting.</p> <h2 id="3-add-dialog-resource-type-validation">3. Add Dialog Resource Type Validation</h2> <p>Even with separate categories, the same <code class="language-plaintext highlighter-rouge">dialog-ready</code> event still fires for every dialog on the page. The safeguard is to verify that the script is running inside the intended dialog before executing any logic.</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
</pre></td><td class="rouge-code"><pre><span class="p">(</span><span class="nf">function </span><span class="p">(</span><span class="nb">document</span><span class="p">,</span> <span class="nx">$</span><span class="p">,</span> <span class="nx">authorUtils</span><span class="p">)</span> <span class="p">{</span>
  <span class="dl">"</span><span class="s2">use strict</span><span class="dl">"</span><span class="p">;</span>

  <span class="kd">var</span> <span class="nx">DIALOG_RESOURCE_TYPE</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">myproject/components/product/wizard</span><span class="dl">"</span><span class="p">;</span>

  <span class="kd">function</span> <span class="nf">isTargetDialog</span><span class="p">(</span><span class="nx">formElement</span><span class="p">,</span> <span class="nx">resourceType</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">resourceTypeInput</span> <span class="o">=</span> <span class="nx">formElement</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="dl">"</span><span class="s2">input[name='./sling:resourceType']</span><span class="dl">"</span><span class="p">);</span>
    <span class="k">return</span> <span class="nx">resourceTypeInput</span><span class="p">.</span><span class="nf">val</span><span class="p">()</span> <span class="o">===</span> <span class="nx">resourceType</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nf">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nf">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">dialog-ready</span><span class="dl">"</span><span class="p">,</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">formElement</span> <span class="o">=</span> <span class="nf">$</span><span class="p">(</span><span class="k">this</span><span class="p">).</span><span class="nf">find</span><span class="p">(</span><span class="dl">"</span><span class="s2">coral-dialog form.foundation-form</span><span class="dl">"</span><span class="p">);</span>

    <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nf">isTargetDialog</span><span class="p">(</span><span class="nx">formElement</span><span class="p">,</span> <span class="nx">DIALOG_RESOURCE_TYPE</span><span class="p">))</span> <span class="p">{</span>
      <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Your dialog-specific logic starts here</span>
    <span class="kd">var</span> <span class="nx">$wizard</span> <span class="o">=</span> <span class="nx">formElement</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="dl">"</span><span class="s2">.wizard-steps</span><span class="dl">"</span><span class="p">);</span>

    <span class="nx">$wizard</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">click</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.wizard-nav-next</span><span class="dl">"</span><span class="p">,</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
      <span class="c1">// advance to next step</span>
    <span class="p">});</span>

    <span class="nx">$wizard</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">click</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.wizard-nav-prev</span><span class="dl">"</span><span class="p">,</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
      <span class="c1">// return to previous step</span>
    <span class="p">});</span>
  <span class="p">});</span>
<span class="p">})(</span><span class="nb">document</span><span class="p">,</span> <span class="nx">Granite</span><span class="p">.</span><span class="nx">$</span><span class="p">,</span> <span class="nx">Granite</span><span class="p">.</span><span class="nx">author</span><span class="p">);</span>
</pre></td></tr></tbody></table></code></pre></div></div> <p><strong>How the guard works:</strong></p> <ol> <li><code class="language-plaintext highlighter-rouge">$(document).on("dialog-ready", ...)</code> fires every time any dialog opens.</li> <li><code class="language-plaintext highlighter-rouge">$(this).find("coral-dialog form.foundation-form")</code> locates the currently active dialog form.</li> <li><code class="language-plaintext highlighter-rouge">formElement.find("input[name='./sling:resourceType']")</code> reads the hidden input that AEM injects into every dialog form - it contains the component’s <code class="language-plaintext highlighter-rouge">sling:resourceType</code>.</li> <li>If the resource type does not match, the handler exits immediately. No event listeners are bound, no DOM is touched.</li> </ol> <p>This pattern costs three lines of overhead but eliminates an entire class of cross-dialog bugs.</p> <h2 id="complete-working-example">Complete Working Example</h2> <p>Here is a full file layout you can copy into your project:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre>apps/
  myproject/
    components/
      product/
        wizard/
          .content.xml          ← component definition
          cq:dialog/
            .content.xml        ← dialog with extraClientlibs
    clientlibs/
      product-wizard-authoring/
        .content.xml            ← clientlib folder
        js/
          dialog.js             ← dialog logic with resource type guard
</pre></td></tr></tbody></table></code></pre></div></div> <p><strong>Dialog definition</strong> (<code class="language-plaintext highlighter-rouge">cq:dialog/.content.xml</code>):</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;jcr:root</span> <span class="na">xmlns:sling=</span><span class="s">"http://sling.apache.org/jcr/sling/1.0"</span>
          <span class="na">xmlns:cq=</span><span class="s">"http://www.day.com/jcr/cq/1.0"</span>
          <span class="na">xmlns:jcr=</span><span class="s">"http://www.jcp.org/jcr/1.0"</span>
          <span class="na">xmlns:nt=</span><span class="s">"http://www.jcp.org/jcr/nt/1.0"</span>
          <span class="na">jcr:primaryType=</span><span class="s">"nt:unstructured"</span>
          <span class="na">sling:resourceType=</span><span class="s">"cq/gui/components/authoring/dialog"</span>
          <span class="na">extraClientlibs=</span><span class="s">"[myproject.components.product.wizard.authoring]"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;content</span> <span class="na">jcr:primaryType=</span><span class="s">"nt:unstructured"</span>
           <span class="na">sling:resourceType=</span><span class="s">"granite/ui/components/coral/foundation/container"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;items</span> <span class="na">jcr:primaryType=</span><span class="s">"nt:unstructured"</span><span class="nt">&gt;</span>
      <span class="c">&lt;!-- your dialog fields here --&gt;</span>
    <span class="nt">&lt;/items&gt;</span>
  <span class="nt">&lt;/content&gt;</span>
<span class="nt">&lt;/jcr:root&gt;</span>
</pre></td></tr></tbody></table></code></pre></div></div> <p><strong>Clientlib folder</strong> (<code class="language-plaintext highlighter-rouge">clientlibs/product-wizard-authoring/.content.xml</code>):</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;jcr:root</span> <span class="na">xmlns:jcr=</span><span class="s">"http://www.jcp.org/jcr/1.0"</span>
          <span class="na">jcr:primaryType=</span><span class="s">"cq:ClientLibraryFolder"</span>
          <span class="na">categories=</span><span class="s">"[myproject.components.product.wizard.authoring]"</span>
          <span class="na">dependencies=</span><span class="s">"[cq.authoring.dialogpage]"</span> <span class="nt">/&gt;</span>
</pre></td></tr></tbody></table></code></pre></div></div> <p><strong>Dialog JS</strong> (<code class="language-plaintext highlighter-rouge">clientlibs/product-wizard-authoring/js/dialog.js</code>):</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="p">(</span><span class="nf">function </span><span class="p">(</span><span class="nb">document</span><span class="p">,</span> <span class="nx">$</span><span class="p">)</span> <span class="p">{</span>
  <span class="dl">"</span><span class="s2">use strict</span><span class="dl">"</span><span class="p">;</span>

  <span class="kd">var</span> <span class="nx">DIALOG_RESOURCE_TYPE</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">myproject/components/product/wizard</span><span class="dl">"</span><span class="p">;</span>

  <span class="kd">function</span> <span class="nf">isTargetDialog</span><span class="p">(</span><span class="nx">form</span><span class="p">,</span> <span class="nx">resourceType</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">form</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="dl">"</span><span class="s2">input[name='./sling:resourceType']</span><span class="dl">"</span><span class="p">).</span><span class="nf">val</span><span class="p">()</span> <span class="o">===</span> <span class="nx">resourceType</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nf">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nf">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">dialog-ready</span><span class="dl">"</span><span class="p">,</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">form</span> <span class="o">=</span> <span class="nf">$</span><span class="p">(</span><span class="k">this</span><span class="p">).</span><span class="nf">find</span><span class="p">(</span><span class="dl">"</span><span class="s2">coral-dialog form.foundation-form</span><span class="dl">"</span><span class="p">);</span>
    <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nf">isTargetDialog</span><span class="p">(</span><span class="nx">form</span><span class="p">,</span> <span class="nx">DIALOG_RESOURCE_TYPE</span><span class="p">))</span> <span class="p">{</span>
      <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Dialog-specific logic below this line</span>
    <span class="c1">// ----------------------------------------</span>
  <span class="p">});</span>
<span class="p">})(</span><span class="nb">document</span><span class="p">,</span> <span class="nx">Granite</span><span class="p">.</span><span class="nx">$</span><span class="p">);</span>
</pre></td></tr></tbody></table></code></pre></div></div> <h2 id="debugging-tips">Debugging Tips</h2> <h3 id="check-which-clientlib-categories-are-loaded">Check which clientlib categories are loaded</h3> <p>Open the browser’s Network tab, filter by <code class="language-plaintext highlighter-rouge">clientlib</code> or your project namespace. When you open a dialog, a new request appears for each extraClientlibs category. If you see a category that should not be there, check the dialog’s <code class="language-plaintext highlighter-rouge">.content.xml</code>.</p> <h3 id="verify-the-resource-type-guard">Verify the resource type guard</h3> <p>In the browser console, after opening a dialog:</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="c1">// Check which resource type the current dialog has</span>
<span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">form.foundation-form input[name="./sling:resourceType"]</span><span class="dl">'</span><span class="p">).</span><span class="nx">value</span><span class="p">;</span>

<span class="c1">// Check all loaded dialog-ready handlers</span>
<span class="nx">$</span><span class="p">.</span><span class="nf">_data</span><span class="p">(</span><span class="nb">document</span><span class="p">,</span> <span class="dl">"</span><span class="s2">events</span><span class="dl">"</span><span class="p">)[</span><span class="dl">"</span><span class="s2">dialog-ready</span><span class="dl">"</span><span class="p">];</span>
</pre></td></tr></tbody></table></code></pre></div></div> <p>The first command tells you whether your guard constant matches the actual resource type. The second shows how many handlers are registered - if you see more than expected, some component is missing its guard.</p> <h3 id="toggle-script-only-clientlibs-for-testing">Toggle script-only clientlibs for testing</h3> <p>If you suspect a specific clientlib is causing a conflict, temporarily remove it from the <code class="language-plaintext highlighter-rouge">extraClientlibs</code> array in the dialog XML and reload the editor. If the problem disappears, you have isolated the culprit.</p> <h2 id="summary">Summary</h2> <table> <thead> <tr> <th>Rule</th> <th>What to do</th> <th>Why</th> </tr> </thead> <tbody> <tr> <td>Avoid global categories</td> <td>Never put component-specific JS in <code class="language-plaintext highlighter-rouge">cq.authoring.dialog</code></td> <td>It runs in every dialog, for every component</td> </tr> <tr> <td>Use descriptive categories</td> <td>Name categories <code class="language-plaintext highlighter-rouge">project.component-group.feature.authoring</code></td> <td>Prevents name collisions across teams and deployments</td> </tr> <tr> <td>Validate resource type</td> <td>Guard every <code class="language-plaintext highlighter-rouge">dialog-ready</code> handler with <code class="language-plaintext highlighter-rouge">sling:resourceType</code> check</td> <td>Prevents cross-dialog bugs when multiple scripts accumulate on the page</td> </tr> </tbody> </table> <p>Following these three rules eliminates an entire category of AEM dialog bugs - the kind that only appear after a specific sequence of dialog opens and disappear on page reload.</p>]]></content><author><name>Evgeniy Fitsner</name><email>drfits@drfits.com</email></author><category term="aem"/><category term="javascript"/><summary type="html"><![CDATA[How AEM dialog extraClientlibs work, why they cause cross-dialog JS conflicts, and three practical rules to keep your component dialogs isolated and maintainable.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://drfits.com/assets/images/posts/2021-02-11-aem-dialog-with-extraclientlibs-do-not-shot-yourself-in-the-foot/cover.webp"/><media:content medium="image" url="https://drfits.com/assets/images/posts/2021-02-11-aem-dialog-with-extraclientlibs-do-not-shot-yourself-in-the-foot/cover.webp" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">OAK Query Aborted</title><link href="https://drfits.com/2020/06/11/oak-query-aborted/" rel="alternate" type="text/html" title="OAK Query Aborted"/><published>2020-06-11T00:00:00+00:00</published><updated>2020-06-11T00:00:00+00:00</updated><id>https://drfits.com/2020/06/11/oak-query-aborted</id><content type="html" xml:base="https://drfits.com/2020/06/11/oak-query-aborted/"><![CDATA[<h2 id="the-problem">The Problem</h2> <p>Every Adobe Experience Manager developer eventually encounters an exception indicating that the node reading limit has been exceeded:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre>java.lang.UnsupportedOperationException: The query read or traversed more than 100000 nodes.
To avoid affecting other tasks, processing was stopped.
	at org.apache.jackrabbit.oak.query.FilterIterators.checkReadLimit(FilterIterators.java:70)
	at org.apache.jackrabbit.oak.plugins.index.Cursors.checkReadLimit(Cursors.java:67)
	at org.apache.jackrabbit.oak.plugins.index.lucene.LucenePropertyIndex$LucenePathCursor$1.next(LucenePropertyIndex.java:1730)
</pre></td></tr></tbody></table></code></pre></div></div> <p>This error occurs when a JCR-SQL2 or XPath query reads or traverses more than 100,000 nodes without finding an applicable index. Oak aborts the query to prevent it from degrading system performance.</p> <h2 id="root-causes">Root Causes</h2> <ul> <li><strong>Missing index</strong>: No Oak index covers the properties used in the query WHERE clause.</li> <li><strong>Missing path restriction</strong>: The query lacks <code class="language-plaintext highlighter-rouge">ISDESCENDANTNODE</code> or <code class="language-plaintext highlighter-rouge">ISCHILDNODE</code>, causing a full repository scan.</li> <li><strong>Leading wildcards</strong>: Using <code class="language-plaintext highlighter-rouge">LIKE '%term%'</code> forces a traversal since standard property indexes cannot satisfy leading wildcard patterns.</li> <li><strong>Inefficient node types</strong>: Querying <code class="language-plaintext highlighter-rouge">nt:unstructured</code> or <code class="language-plaintext highlighter-rouge">sling:OrderedFolder</code> when a more specific type would suffice.</li> </ul> <h2 id="solutions">Solutions</h2> <h3 id="production-fixes">Production Fixes</h3> <ol> <li> <p><strong>Define an Oak index</strong> for the properties used in your query. Create a property index for equality lookups or a Lucene index for full-text search.</p> </li> <li> <p><strong>Add a path restriction</strong> to every query using <code class="language-plaintext highlighter-rouge">ISDESCENDANTNODE</code> or <code class="language-plaintext highlighter-rouge">ISCHILDNODE</code>. This limits the search scope and helps the query engine select the correct index.</p> </li> <li> <p><strong>Update existing indexes</strong> to cover additional properties if your query has evolved.</p> </li> <li> <p><strong>Use efficient node types</strong>: prefer <code class="language-plaintext highlighter-rouge">oak:Unstructured</code> over <code class="language-plaintext highlighter-rouge">nt:unstructured</code> and <code class="language-plaintext highlighter-rouge">sling:Folder</code> over <code class="language-plaintext highlighter-rouge">sling:OrderedFolder</code> when ordering is not required.</p> </li> <li> <p><strong>Consider architectural changes</strong>: if queries consistently hit the limit, the repository structure may need reorganization.</p> </li> </ol> <h3 id="development-workaround">Development Workaround</h3> <p>During development only, you can temporarily increase the read limits:</p> <ol> <li>Navigate to QueryEngineSettings in the JMX console:</li> </ol> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>http://localhost:4502/system/console/jmx/org.apache.jackrabbit.oak%3Aname%3Dsettings%2Ctype%3DQueryEngineSettings
</pre></td></tr></tbody></table></code></pre></div></div> <ol> <li>Increase the <code class="language-plaintext highlighter-rouge">LimitReads</code> and <code class="language-plaintext highlighter-rouge">LimitInMemory</code> parameters.</li> </ol> <p><strong>Warning</strong>: This is a development-only workaround. Never deploy increased limits to production, as it masks underlying performance problems and can cause system instability.</p> <h2 id="further-reading">Further Reading</h2> <p>For a comprehensive guide to JCR-SQL2 query syntax, indexing strategies, and debugging techniques, see the <a href="/2016/12/03/jcr-sql2-query-with-examples/">JCR-SQL2 Query with Examples</a> post.</p>]]></content><author><name>Evgeniy Fitsner</name><email>drfits@drfits.com</email></author><category term="aem"/><category term="apache-oak"/><category term="apache-sling"/><summary type="html"><![CDATA[Understand and resolve the OAK query node read limit exceeded error in Adobe AEM.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://drfits.com/assets/images/posts/2020-06-11-oak-query-aborted/cover.webp"/><media:content medium="image" url="https://drfits.com/assets/images/posts/2020-06-11-oak-query-aborted/cover.webp" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">AEM Digital Assets Installation</title><link href="https://drfits.com/2020/03/16/aem-digital-assets-installation/" rel="alternate" type="text/html" title="AEM Digital Assets Installation"/><published>2020-03-16T00:00:00+00:00</published><updated>2020-03-16T00:00:00+00:00</updated><id>https://drfits.com/2020/03/16/aem-digital-assets-installation</id><content type="html" xml:base="https://drfits.com/2020/03/16/aem-digital-assets-installation/"><![CDATA[<h2 id="introduction">Introduction</h2> <p>When you need to install a large number of digital assets on AEM, the straightforward approach of creating a content package and deploying it through Package Manager can become problematic. The asset rendition workflows trigger for each asset during installation, which can cause the process to hang or take an extremely long time.</p> <h2 id="the-solution">The Solution</h2> <p>Temporarily disable the workflow launcher during package installation to prevent rendition workflows from blocking the import process.</p> <h2 id="steps">Steps</h2> <ol> <li> <p>Create a content package containing the digital assets you want to install.</p> </li> <li> <p>Stop the workflow launcher by disabling the OSGi component:</p> </li> </ol> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>com.day.cq.workflow.launcher.impl.WorkflowLauncherImpl
</pre></td></tr></tbody></table></code></pre></div></div> <p>You can do this through the OSGi console at <code class="language-plaintext highlighter-rouge">http://localhost:4502/system/console/components</code>.</p> <ol> <li> <p>Install the content package from step 1 through Package Manager.</p> </li> <li> <p>Re-enable the workflow launcher by starting <code class="language-plaintext highlighter-rouge">WorkflowLauncherImpl</code> again.</p> </li> </ol> <h2 id="why-this-works">Why This Works</h2> <p>By disabling the workflow launcher, AEM imports the asset nodes directly without triggering the DAM Update Asset workflow for each file. Once the launcher is re-enabled, any new assets uploaded through the normal UI will process renditions as expected.</p> <p>This approach is particularly useful when migrating assets from another system or restoring a large asset library from backup.</p>]]></content><author><name>Evgeniy Fitsner</name><email>drfits@drfits.com</email></author><category term="aem"/><summary type="html"><![CDATA[Install digital assets on AEM efficiently by temporarily disabling the workflow launcher.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://drfits.com/assets/images/posts/2020-03-16-aem-digital-assets-installation/cover.webp"/><media:content medium="image" url="https://drfits.com/assets/images/posts/2020-03-16-aem-digital-assets-installation/cover.webp" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Find Out AEM Product Version</title><link href="https://drfits.com/2020/02/28/find-out-aem-product-version/" rel="alternate" type="text/html" title="Find Out AEM Product Version"/><published>2020-02-28T00:00:00+00:00</published><updated>2020-02-28T00:00:00+00:00</updated><id>https://drfits.com/2020/02/28/find-out-aem-product-version</id><content type="html" xml:base="https://drfits.com/2020/02/28/find-out-aem-product-version/"><![CDATA[<h2 id="introduction">Introduction</h2> <p>Knowing the exact AEM product version is essential for troubleshooting, compatibility checks, and determining which features are available. AEM provides a dedicated endpoint that displays this information.</p> <h2 id="how-to-check">How to Check</h2> <p>Navigate to the system console product information page:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>http://localhost:4502/system/console/status-productinfo
</pre></td></tr></tbody></table></code></pre></div></div> <p>This page displays:</p> <ul> <li>The installed AEM version (for example, Adobe Experience Manager 6.5.0)</li> <li>AEM Service Pack level</li> <li>Hotfix information</li> <li>Other installed Adobe product details</li> </ul> <h2 id="alternative-methods">Alternative Methods</h2> <p>You can also find version information through:</p> <ul> <li><strong>CRXDE Lite</strong>: The welcome screen shows the AEM version</li> <li><strong>Package Manager</strong>: Installed service packs and hotfixes are listed</li> <li><strong>OSGi Console</strong>: Bundle versions can indicate the AEM release</li> </ul>]]></content><author><name>Evgeniy Fitsner</name><email>drfits@drfits.com</email></author><category term="aem"/><summary type="html"><![CDATA[Quickly identify the installed AEM product version using the system console.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://drfits.com/assets/images/posts/2020-02-28-find-out-aem-product-version/cover.webp"/><media:content medium="image" url="https://drfits.com/assets/images/posts/2020-02-28-find-out-aem-product-version/cover.webp" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Enable AEM Javadoc Hints</title><link href="https://drfits.com/2020/02/12/enable-aem-javadoc-hints/" rel="alternate" type="text/html" title="Enable AEM Javadoc Hints"/><published>2020-02-12T00:00:00+00:00</published><updated>2020-02-12T00:00:00+00:00</updated><id>https://drfits.com/2020/02/12/enable-aem-javadoc-hints</id><content type="html" xml:base="https://drfits.com/2020/02/12/enable-aem-javadoc-hints/"><![CDATA[<h2 id="introduction">Introduction</h2> <p>To get the most out of your IDE when developing for AEM, you should enable Javadoc hints. These provide inline API documentation, parameter descriptions, and usage examples directly in your editor.</p> <h2 id="steps-for-intellij-idea">Steps for IntelliJ IDEA</h2> <ol> <li> <p>Open <strong>Project Structure</strong> (shortcut on Windows: <code class="language-plaintext highlighter-rouge">Ctrl + Alt + Shift + S</code>, on macOS: <code class="language-plaintext highlighter-rouge">Cmd + ;</code>).</p> </li> <li> <p>Under <strong>Libraries</strong>, find the Uber JAR dependency you are using (for example, <code class="language-plaintext highlighter-rouge">Maven: com.adobe.aem:uber-jar:apis:6.4.4</code>).</p> </li> <li> <p>Click the <strong>plus icon with the Earth</strong> symbol to add documentation.</p> </li> <li> <p>Enter the Javadoc URL for your AEM version:</p> </li> </ol> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>https://helpx.adobe.com/experience-manager/6-4/sites/developing/using/reference-materials/javadoc/index.html
</pre></td></tr></tbody></table></code></pre></div></div> <p>Adjust the version number in the URL to match your AEM release.</p> <ol> <li>Click <strong>OK</strong> to save.</li> </ol> <h2 id="using-the-documentation">Using the Documentation</h2> <p>After configuration, you can view API documentation hints by pressing <code class="language-plaintext highlighter-rouge">Ctrl + Q</code> on Windows or <code class="language-plaintext highlighter-rouge">F1</code> on macOS while your cursor is on any AEM API class or method.</p> <h2 id="javadoc-urls-by-version">Javadoc URLs by Version</h2> <table> <thead> <tr> <th>AEM Version</th> <th>Javadoc URL</th> </tr> </thead> <tbody> <tr> <td>6.5</td> <td><code class="language-plaintext highlighter-rouge">https://helpx.adobe.com/experience-manager/6-5/sites/developing/using/reference-materials/javadoc/index.html</code></td> </tr> <tr> <td>6.4</td> <td><code class="language-plaintext highlighter-rouge">https://helpx.adobe.com/experience-manager/6-4/sites/developing/using/reference-materials/javadoc/index.html</code></td> </tr> <tr> <td>6.3</td> <td><code class="language-plaintext highlighter-rouge">https://helpx.adobe.com/experience-manager/6-3/sites/developing/using/reference-materials/javadoc/index.html</code></td> </tr> </tbody> </table>]]></content><author><name>Evgeniy Fitsner</name><email>drfits@drfits.com</email></author><category term="aem"/><category term="intellij"/><summary type="html"><![CDATA[Configure your IDE to show AEM API documentation hints for a better development experience.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://drfits.com/assets/images/posts/2020-02-12-enable-aem-javadoc-hints/cover.webp"/><media:content medium="image" url="https://drfits.com/assets/images/posts/2020-02-12-enable-aem-javadoc-hints/cover.webp" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">How to Install HP LaserJet 1018 Printer on Windows 10</title><link href="https://drfits.com/2020/01/17/how-to-install-printer-hp-1018-in-windows-10/" rel="alternate" type="text/html" title="How to Install HP LaserJet 1018 Printer on Windows 10"/><published>2020-01-17T00:00:00+00:00</published><updated>2020-01-17T00:00:00+00:00</updated><id>https://drfits.com/2020/01/17/how-to-install-printer-hp-1018-in-windows-10</id><content type="html" xml:base="https://drfits.com/2020/01/17/how-to-install-printer-hp-1018-in-windows-10/"><![CDATA[<h2 id="introduction">Introduction</h2> <p>Windows 10 usually detects and installs printers automatically. However, older printers like the HP LaserJet 1018 may not be recognized, requiring manual installation through the Print Server Properties dialog.</p> <h2 id="installation-steps">Installation Steps</h2> <ol> <li> <p><strong>Open Print Server Properties</strong></p> <p>Press <code class="language-plaintext highlighter-rouge">Win + R</code>, enter <code class="language-plaintext highlighter-rouge">printui /s /t2</code>, and click OK. This opens the Print Server Properties window directly to the Drivers tab.</p> </li> <li> <p><strong>Add a Printer Driver</strong></p> <p>Click the <strong>Add</strong> button. The Add Printer Driver Wizard will open. Click <strong>Next</strong> to proceed.</p> </li> <li> <p><strong>Select the Driver</strong></p> <ul> <li>If the HP LaserJet 1018 driver appears in the list, select it and click <strong>Next</strong>.</li> <li>If not, click <strong>Windows Update</strong> to refresh the driver list. This may take a few minutes.</li> <li>After the list updates, select manufacturer <strong>HP</strong> and choose the <strong>HP LaserJet 1020</strong> driver. The 1020 driver is compatible with the 1018 model.</li> <li>Click <strong>Next</strong>, then <strong>Finish</strong>.</li> </ul> </li> <li> <p><strong>Complete Setup</strong></p> <p>Connect the printer via USB if you have not already done so. Windows should now recognize the device using the installed driver.</p> </li> </ol> <h2 id="troubleshooting">Troubleshooting</h2> <ul> <li>If the printer still does not work, try downloading the driver directly from the HP support website.</li> <li>Ensure the USB cable is functioning and the printer is powered on.</li> <li>Check Device Manager for any unknown USB devices that may correspond to the printer.</li> </ul>]]></content><author><name>Evgeniy Fitsner</name><email>drfits@drfits.com</email></author><category term="windows"/><summary type="html"><![CDATA[Manually install the HP LaserJet 1018 printer on Windows 10 when automatic detection fails.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://drfits.com/assets/images/posts/2020-01-17-how-to-install-printer-hp-1018-in-windows-10/cover.webp"/><media:content medium="image" url="https://drfits.com/assets/images/posts/2020-01-17-how-to-install-printer-hp-1018-in-windows-10/cover.webp" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Fix Java Windows Registry Warning</title><link href="https://drfits.com/2018/09/26/fix-java-warnings/" rel="alternate" type="text/html" title="Fix Java Windows Registry Warning"/><published>2018-09-26T00:00:00+00:00</published><updated>2018-09-26T00:00:00+00:00</updated><id>https://drfits.com/2018/09/26/fix-java-warnings</id><content type="html" xml:base="https://drfits.com/2018/09/26/fix-java-warnings/"><![CDATA[<h2 id="the-problem">The Problem</h2> <p>When executing Java code that interacts with the Windows registry - particularly when using the FindBugs Maven plugin or running IntelliJ IDEA - you may encounter the following warning:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0x80000002. Windows RegCreateKeyEx(...) returned error code 5.
</pre></td></tr></tbody></table></code></pre></div></div> <p>Error code 5 means “Access Denied.” The Java Preferences API attempts to store system-level preferences in the Windows registry but lacks the necessary permissions to create the required registry keys.</p> <h2 id="solution">Solution</h2> <p>Create the missing registry keys with appropriate permissions:</p> <ul> <li><code class="language-plaintext highlighter-rouge">HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs</code></li> <li><code class="language-plaintext highlighter-rouge">HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\JavaSoft\Prefs</code></li> </ul> <h2 id="step-by-step-fix">Step-by-Step Fix</h2> <ol> <li> <p>Open the Registry Editor by pressing <code class="language-plaintext highlighter-rouge">Win + R</code>, typing <code class="language-plaintext highlighter-rouge">regedit</code>, and pressing Enter.</p> </li> <li> <p>Navigate to <code class="language-plaintext highlighter-rouge">Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node</code>.</p> </li> <li> <p>Right-click on <code class="language-plaintext highlighter-rouge">WOW6432Node</code>, select <strong>New</strong> then <strong>Key</strong>, and name it <strong>JavaSoft</strong>.</p> </li> <li> <p>Inside the new <code class="language-plaintext highlighter-rouge">JavaSoft</code> key, create another key named <strong>Prefs</strong>.</p> </li> <li> <p>Repeat steps 2 through 4 for the <code class="language-plaintext highlighter-rouge">HKEY_LOCAL_MACHINE\SOFTWARE</code> path (without the <code class="language-plaintext highlighter-rouge">WOW6432Node</code> portion).</p> </li> </ol> <p>After creating both keys, the warning will no longer appear when running Java applications.</p>]]></content><author><name>Evgeniy Fitsner</name><email>drfits@drfits.com</email></author><category term="java"/><category term="windows"/><summary type="html"><![CDATA[Resolve the Windows Java preferences registry warning that appears when running Java tools.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://drfits.com/assets/images/posts/2018-09-26-fix-java-warnings/cover.webp"/><media:content medium="image" url="https://drfits.com/assets/images/posts/2018-09-26-fix-java-warnings/cover.webp" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Find Out AEM Information</title><link href="https://drfits.com/2018/09/22/find-out-aem-information/" rel="alternate" type="text/html" title="Find Out AEM Information"/><published>2018-09-22T00:00:00+00:00</published><updated>2018-09-22T00:00:00+00:00</updated><id>https://drfits.com/2018/09/22/find-out-aem-information</id><content type="html" xml:base="https://drfits.com/2018/09/22/find-out-aem-information/"><![CDATA[<h2 id="introduction">Introduction</h2> <p>When working with AEM, you often need to quickly check system information such as the installed version, active run modes, or bundle status. This post collects the most useful URLs for accessing this information.</p> <h2 id="installed-aem-version">Installed AEM Version</h2> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>http://localhost:4502/system/console/status-productinfo
</pre></td></tr></tbody></table></code></pre></div></div> <p>Displays the AEM product version, service pack level, and hotfix information.</p> <h2 id="aem-run-mode">AEM Run Mode</h2> <ol> <li>Navigate to the OSGi bundles console:</li> </ol> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>http://localhost:4502/system/console/bundles
</pre></td></tr></tbody></table></code></pre></div></div> <ol> <li>Go to <strong>Status</strong> then <strong>Sling Settings</strong> in the navigation menu.</li> </ol> <p>This page shows the active run modes (for example, <code class="language-plaintext highlighter-rouge">author</code>, <code class="language-plaintext highlighter-rouge">publish</code>, <code class="language-plaintext highlighter-rouge">localdev</code>, <code class="language-plaintext highlighter-rouge">nosamplecontent</code>), which determine how AEM loads configurations and content.</p> <h2 id="additional-useful-urls">Additional Useful URLs</h2> <table> <thead> <tr> <th>Information</th> <th>URL</th> </tr> </thead> <tbody> <tr> <td>OSGi Configuration Manager</td> <td><code class="language-plaintext highlighter-rouge">/system/console/configMgr</code></td> </tr> <tr> <td>Bundle Status</td> <td><code class="language-plaintext highlighter-rouge">/system/console/bundles</code></td> </tr> <tr> <td>System Console Home</td> <td><code class="language-plaintext highlighter-rouge">/system/console</code></td> </tr> <tr> <td>CRXDE Lite</td> <td><code class="language-plaintext highlighter-rouge">/crx/de</code></td> </tr> <tr> <td>Package Manager</td> <td><code class="language-plaintext highlighter-rouge">/crx/packmgr</code></td> </tr> <tr> <td>Query Debugger</td> <td><code class="language-plaintext highlighter-rouge">/libs/cq/search/content/querydebug.html</code></td> </tr> <tr> <td>JMX Console</td> <td><code class="language-plaintext highlighter-rouge">/system/console/jmx</code></td> </tr> </tbody> </table>]]></content><author><name>Evgeniy Fitsner</name><email>drfits@drfits.com</email></author><category term="aem"/><summary type="html"><![CDATA[Quick reference URLs for locating AEM version, run mode, and system configuration details.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://drfits.com/assets/images/posts/2018-09-22-find-out-aem-information/cover.webp"/><media:content medium="image" url="https://drfits.com/assets/images/posts/2018-09-22-find-out-aem-information/cover.webp" xmlns:media="http://search.yahoo.com/mrss/"/></entry></feed>