AEM interviews have a strange problem: the most-shared question lists online were written for AEM 6.2 or 6.3, and many of their answers are now wrong. They tell you to register servlets by path, configure OSGi in the Felix console, use getAdministrativeResourceResolver, or set statfileslevel to "the number of levels to flush". If you repeat those answers to a senior interviewer in 2026, you'll lose points you didn't need to lose.
This guide is the list I wish I'd had. Every answer has been checked against the official Adobe Experience League docs and the Apache Sling, Oak, and Felix documentation, and where AEM as a Cloud Service (AEMaaCS) and AEM 6.5 behave differently, the answer says so. Answers are deliberately short, two to six sentences, because that's the length of a good spoken answer. When you want the full story, each answer links to the deeper post on this blog.
How to use this guide
The questions are grouped by area and tagged by level:
- (Junior): 0–2 years. You should know these cold.
- (Mid): 2–5 years. The questions that decide most developer interviews.
- (Senior): 5+ years. Tradeoffs, internals, and "why", not just "what".
- (Architect): system design and scenario questions where there's no single correct answer, only good and bad reasoning.
Don't memorise answers. Read the answer, then try to explain it out loud in your own words, ideally while pointing at real code or a running local instance. Interviewers can tell a recited answer from an understood one within about ten seconds, and the "What interviewers look for" notes on the trickier questions tell you what separates the two.
If you're earlier in your journey, start with the AEM Developer Roadmap and the AEM Developer Cheat Sheet, then come back here.
Fundamentals
1. What is AEM? (Junior)
Adobe Experience Manager is a Java-based content management system (Sites) combined with a digital asset management system (Assets), part of Adobe Experience Cloud. Authors create pages, fragments, and assets, and AEM delivers them as HTML, as JSON for headless apps, or both. It's built on open-source foundations: a JCR repository (Apache Jackrabbit Oak), the Apache Sling web framework, and an OSGi runtime (Apache Felix). It's available as AEM as a Cloud Service, as AEM 6.5 (on-premise or Adobe Managed Services), and with Edge Delivery Services as a newer delivery option. The cheat sheet maps out the whole stack.
2. What is the AEM technology stack? (Junior)
From the bottom up: Oak stores everything as a tree of nodes and properties (the JCR, JSR-283). Sling maps each HTTP request to a resource in that tree and picks a script to render it. OSGi (Felix) is the module system your Java code runs in, as bundles, components, and services. HTL is the templating language that produces markup. Maven builds your code into OSGi bundles and content packages. Apache HTTP Server with the Dispatcher module caches and filters traffic in front of publish.
A one-line summary worth saying out loud: everything is a resource in the JCR, Sling turns a request into a resource plus a script, and OSGi runs your Java.
3. What are the author, publish, and dispatcher tiers? (Junior)
Author is where content is created and edited. It's behind a login and not publicly exposed. Publish serves the approved content to visitors. When an author publishes (activates) content, it's replicated from author to publish. The Dispatcher is an Apache module in front of publish that caches rendered responses as files, filters out unwanted requests, and can load-balance. In AEMaaCS, a CDN sits in front of the Dispatcher, and there's an optional preview tier for reviewing content before it goes live. See the architecture guide.
4. What does "everything is content" mean? (Junior)
In AEM, pages, components' authored values, configuration (/conf), templates, user accounts, and even code (/apps) are all stored as nodes and properties in the JCR. There's no separate relational schema for content. This lets Sling treat every URL as a path into one hierarchy, and it's why tools like CRXDE Lite and content packages work on code and content alike. Binaries (images, PDFs) are the exception in storage terms. They're referenced from nodes but stored in a separate blob/data store. The JCR and Oak guide goes deeper.
5. What is Apache Sling? (Junior)
Sling is a REST-style web framework that resolves every request to a resource (usually a JCR node) instead of routing it to a controller. It decomposes the URL into path, selectors, extension, and suffix, finds the resource, reads its sling:resourceType, and picks a script or servlet registered for that type. Sling also provides the Resource/ResourceResolver API, Sling Models, servlets, filters, the scheduler, and the job queue. The Sling guide covers all of it.
6. What is OSGi and why does AEM use it? (Junior)
OSGi is a module system and service platform for Java. Code is packaged as bundles (JARs with extra manifest headers declaring what packages they import and export), and bundles publish and consume services through a registry. AEM uses Apache Felix as the OSGi framework. It lets bundles be installed, updated, and configured at runtime without restarting the JVM, and it keeps modules isolated through explicit imports and exports. The OSGi guide goes deeper.
7. How does replication work? (Junior/Mid)
On AEM 6.5, replication is done by replication agents: the default agent on author pushes content packages to each publish instance over HTTP, and Dispatcher Flush agents send invalidation requests to the Dispatcher. On AEMaaCS, replication agents no longer exist. Publishing uses Sling Content Distribution through an Adobe-managed pipeline, and cache invalidation is handled for you. Reverse replication (publish back to author) is not supported in AEMaaCS. The Replicator Java API still works in both.
8. What are run modes? (Junior)
Run modes tune an instance for its role and environment, and select which OSGi configurations apply. Every instance has a service run mode (author or publish). On AEMaaCS the only environment run modes are dev, stage, prod, and rde. Custom run modes aren't possible. On 6.5 you can define your own (for example publish,uat) at startup. Configs live in folders such as config.author.prod or config.publish.
What interviewers look for: that you know custom run modes are gone on Cloud Service, and that environment-specific values there should use Cloud Manager environment variables rather than more run-mode folders.
9. What are /apps, /libs, /content, and /conf? (Junior)
/libs holds Adobe's product code. Never modify it, because upgrades overwrite it. /apps holds your project's code (components, templates' supporting scripts, clientlibs, OSGi configs). /content holds pages, Experience Fragments, and assets (/content/dam). /conf holds context-aware configuration: editable templates, content policies, Content Fragment Models, and CA Config. On AEMaaCS, /apps and /libs are immutable at runtime and can only change through a Cloud Manager deployment.
10. What is an overlay? (Mid)
Sling's resource resolver searches /apps before /libs (the search path), so a node at the same relative path in /apps is found first. Combined with the Sling Resource Merger, an overlay in /apps only needs to contain the properties or child nodes you change, and properties like sling:hideProperties, sling:hideChildren, and sling:orderBefore let you hide or reorder inherited parts. Overlays are for customising product UI (for example a console or dialog). For your own components, prefer inheritance through sling:resourceSuperType. On AEMaaCS, Adobe restricts which /libs areas can be overlaid, and overlaying something marked internal can break on the next automatic update.
11. How is a standard AEM Maven project structured? (Junior/Mid)
The AEM Project Archetype generates modules with clear jobs: core (the Java OSGi bundle: Sling Models, services, servlets), ui.apps (immutable code under /apps: components, clientlibs), ui.config (OSGi configurations and repoinit scripts), ui.content (mutable content: /content, /conf), ui.frontend (webpack build for CSS/JS), dispatcher (Apache and Dispatcher config), all (a container package embedding everything), plus it.tests and ui.tests. Packages are typed as application (code), content (mutable), or container. AEMaaCS enforces the split between mutable and immutable content.
Apache Sling
12. Walk through Sling request processing. (Mid)
- Decompose the URL into resource path, selectors, extension, and suffix.
- Resolve the resource. The
ResourceResolverapplies mappings (/etc/map, vanity paths, aliases) and finds the longest path that matches an existing resource. If nothing matches, you get aNonExistingResourceand usually a 404. - Read the resource type, from
sling:resourceType, or the node type if the property is missing. - Resolve the script or servlet for that type, matching selectors, extension, and HTTP method. It searches
/appsthen/libs. - Fall back up the hierarchy: if nothing matches, try the
sling:resourceSuperTypechain, and finallysling/servlet/default.
Try it with the Sling Resolver Simulator.
13. Decompose /content/site/en/page.mobile.a4.html/foo/bar?x=1. (Junior)
The resource path is /content/site/en/page, the longest prefix that matches an existing resource. The selectors are mobile and a4, the extension is html, and the suffix is /foo/bar. x=1 is a request parameter, which isn't part of Sling's resolution. Selectors and suffix are available through request.getRequestPathInfo().
What interviewers look for: that the path is determined by existing resources, not by the first dot. The suffix can contain dots and slashes. Also, the Dispatcher never caches a request with a query string unless
/ignoreUrlParamsallows it.
14. How does Sling choose between multiple matching scripts? (Mid)
Per the Sling docs, when several scripts or servlets match, Sling prefers the one with the most matching selectors plus extension, then the one registered to the resource type closest to the requested type (a direct match beats a match inherited through sling:resourceSuperType), then the highest service ranking. So page.mobile.html beats page.html for a .mobile.html request. For GET requests with the html extension, a script named after the last segment of the resource type (for example page.html for mysite/components/page) is the default.
15. What is the difference between sling:resourceType and sling:resourceSuperType? (Junior)
sling:resourceType is set on a content node and says which component renders it. It's the link between content and code. sling:resourceSuperType is set on a component node and says which component it inherits from. Scripts, dialogs, and behaviour not defined locally are looked up on the super type. That's how the proxy pattern works: mysite/components/title has sling:resourceSuperType = core/wcm/components/title/v3/title.
16. Resource vs Node: which should you use? (Mid)
Prefer the Sling Resource API. It's an abstraction that works over the JCR and over other resource providers, it's what Sling Models and HTL use, and ValueMap gives you typed, null-safe property access. Drop to the JCR Node API (via resource.adaptTo(Node.class)) only when you need JCR-specific features such as versioning, locking, node types and mixins, or workspace operations. Mixing both carelessly leads to transient-state surprises, because both write through the same underlying session.
17. What does adaptTo() do? (Junior/Mid)
adaptTo(Class) is Sling's adapter pattern. It converts one object into another type without the caller knowing how: resource.adaptTo(ValueMap.class), resource.adaptTo(Page.class) (a legacy WCM adapter; prefer PageManager.getContainingPage), resolver.adaptTo(Session.class), request.adaptTo(MyModel.class). Adaptations come from AdapterFactory services or from the adaptable itself. It returns null when adaptation isn't possible, so always null-check or use ModelFactory, which throws with a useful message.
18. What is a Sling Model, and request vs resource adaptable? (Mid)
A Sling Model is an annotation-driven POJO (@Model) that Sling populates by injection, used to keep logic out of HTL. Resource-adaptable models can be created from any resource, such as in a scheduled job or another model. Request-adaptable models can also reach request-scoped things: selectors, the current page, script bindings (@ScriptVariable), and request attributes. Use SlingHttpServletRequest as the adaptable for component models. Core Components' models are request-based, so delegating to them requires it.
@Model(adaptables = SlingHttpServletRequest.class,
adapters = Teaser.class,
resourceType = "mysite/components/teaser",
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TeaserImpl implements Teaser {
@ValueMapValue private String title;
@ScriptVariable private Page currentPage;
@OSGiService private LinkService links;
@PostConstruct
protected void init() { /* derived values */ }
}19. Which injectors do you know, and why avoid @Inject? (Mid)
The injector-specific annotations: @ValueMapValue (resource properties), @ChildResource, @ScriptVariable (Sling bindings such as currentPage), @OSGiService, @RequestAttribute, @ResourcePath, @Self (the adaptable itself, or a model adapted from it), @SlingObject (resolver, request, resource), and @ContextAwareConfiguration. A plain @Inject tries every injector in order and takes the first non-null value. The Sling docs now discourage it, because it's slower and a property could accidentally be satisfied by the wrong source. See the annotations reference.
20. Required vs optional injection, and @PostConstruct? (Mid)
By default, injected fields are required: if any can't be injected, model creation fails, and adaptTo returns null. Mark individual fields optional (injectionStrategy = InjectionStrategy.OPTIONAL on the injector annotation, or wrap in Optional), or set defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL on @Model to flip the default. @PostConstruct runs after all injections, which is the place for derived values. Using ModelFactory.createModel() instead of adaptTo gives you the actual exception explaining which injection failed.
21. How do you extend a Core Component's Sling Model? (Senior)
You can't subclass the implementation, because *Impl classes are internal. Use the delegation pattern: implement the Core Component's public interface, inject the super type's model with @Self @Via(type = ResourceSuperType.class), delegate every method you don't change, and override the ones you do. Register your model with adapters = Title.class and your proxy's resourceType so it wins for your component.
@Model(adaptables = SlingHttpServletRequest.class, adapters = Title.class,
resourceType = "mysite/components/title")
public class MyTitle implements Title {
@Self @Via(type = ResourceSuperType.class)
private Title delegate;
@Override public String getText() { return delegate.getText().toUpperCase(); }
@Override public String getType() { return delegate.getType(); }
// ...delegate the rest
}What interviewers look for: delegation instead of copy-paste, awareness that new interface methods added in later Core Components versions must be delegated too, and the Core Components guide mindset of changing as little as possible.
22. What is the Sling Model Exporter? (Mid)
Add @Exporter(name = "jackson", extensions = "json") to a model with a resourceType, and Sling registers a servlet that serialises the model to JSON using Jackson, under the model selector: /content/page/jcr:content/root/teaser.model.json. Getters become JSON properties, and you control output with Jackson annotations (@JsonIgnore, @JsonProperty). Core Components and the SPA Editor rely on this for .model.json output. Be aware that anything with a getter is exposed publicly unless ignored.
23. Servlet by path vs by resource type: which and why? (Mid)
Prefer resource type registration (@SlingServletResourceTypes). The Sling docs discourage path binding because path-bound servlets can't be access-controlled with JCR ACLs, don't get suffix handling, are invisible to someone browsing the repository, and only work under the resolver's configured execution paths (servletresolver.paths). With resource types, the servlet only answers when a resource of that type is requested, so repository permissions apply naturally.
@Component(service = Servlet.class)
@SlingServletResourceTypes(resourceTypes = "mysite/components/page",
selectors = "search", extensions = "json",
methods = HttpConstants.METHOD_GET)
public class SearchServlet extends SlingSafeMethodsServlet { /* ... */ }What interviewers look for: the ACL argument, not just "Adobe says so". Bonus: you'd also have to allow the path in the Dispatcher filters.
24. SlingSafeMethodsServlet vs SlingAllMethodsServlet? (Junior)
SlingSafeMethodsServlet handles only "safe", read-only methods: GET, HEAD, OPTIONS, TRACE. SlingAllMethodsServlet extends it and adds POST, PUT, DELETE, and so on. Use the safe variant for anything read-only, which makes intent clear and avoids exposing write methods. Remember that authenticated POSTs need a CSRF token (see question 101).
25. What are Sling Filters and their scopes? (Mid)
A Sling Filter is a javax.servlet.Filter registered as an OSGi service and applied inside Sling's processing. Scopes: REQUEST (once per incoming request), COMPONENT (every component render, including includes and forwards), INCLUDE, FORWARD, and ERROR. Register with @SlingServletFilter(scope = ..., pattern = ..., resourceTypes = ...), and order with service.ranking. Keep them light. A COMPONENT-scope filter runs for every component on every page.
26. What are resolve() and map(), and what is /etc/map? (Mid)
ResourceResolver.resolve(request, path) turns an incoming URL into a resource, applying mappings. map(path) does the reverse: it turns a repository path into the public URL to write into links. /etc/map (and the resolver factory's mapping configuration, plus sling:alias and sling:vanityPath) defines those rules, for example hiding /content/site/en behind /en. Mapping on publish and rewriting in Apache must agree, or links and Dispatcher invalidation drift apart. On AEMaaCS, /etc/map still works, but many teams shorten URLs at the Dispatcher or CDN instead.
27. Sling Scheduler vs Sling Jobs? (Mid/Senior)
The Scheduler triggers code on a cron or period. It's simple, but offers no guarantee: if the instance is down or recycled, that run is lost. On a cluster, use scheduler.runOn=SINGLE or LEADER so it doesn't run on every node. Sling Jobs are persisted in the repository, retried on failure, and processed at least once, even across restarts. On AEMaaCS, Adobe's development guidelines say to use Sling Jobs for anything that must run, since instances can be stopped at any time. A common pattern is a scheduler that only enqueues a job.
28. What is Context-Aware Configuration? (Senior)
CA Config (Sling) lets configuration vary by content location. You define an annotation interface, store values under /conf/<context>/sling:configs/..., and link content to a configuration with sling:configRef on a content root. Code then reads resource.adaptTo(ConfigurationBuilder.class).as(MyConfig.class), and the same code returns brand A's values under /content/brand-a and brand B's under /content/brand-b, with inheritance and fallbacks. It's the right tool for per-site settings that authors or developers maintain as content (API keys excluded; those are secrets).
29. How do you unit test a Sling Model? (Mid)
Use AEM Mocks (io.wcm.testing.aem-mock) with JUnit 5: AemContextExtension gives you an AemContext that can load JSON content, register OSGi services and mocks, set the current resource or page, and adapt to your model. Choose the resource-resolver type deliberately: RESOURCERESOLVER_MOCK is fastest, and JCR_MOCK or JCR_OAK is needed if your code uses the JCR API or queries. Cloud Manager's code-quality step fails as "Important" below 50% coverage. See the unit testing guide.
OSGi
30. Bundle vs component vs service? (Junior)
A bundle is the deployable unit: a JAR with OSGi manifest headers. A component is a class inside a bundle whose lifecycle is managed by Declarative Services (@Component). A service is an object registered in the service registry under an interface, so others can look it up. A component can provide a service (service = MyService.class), but it doesn't have to. For example, a scheduler component might only consume services.
31. What are the bundle lifecycle states? (Junior/Mid)
INSTALLED, RESOLVED, STARTING, ACTIVE, STOPPING, UNINSTALLED. A bundle stuck in Installed almost always has an unresolved Import-Package: a dependency isn't deployed, or it's a version the platform doesn't export. The Felix console (/system/console/bundles locally, or the Developer Console on AEMaaCS) shows the missing import. Fix it by embedding the library or aligning versions, not by marking imports optional to hide the problem.
32. Which annotations do you use for OSGi components? (Mid)
The official OSGi DS and Metatype annotations (org.osgi.service.component.annotations and org.osgi.service.metatype.annotations): @Component, @Reference, @Activate, @Modified, @Deactivate, plus @ObjectClassDefinition, @AttributeDefinition, and @Designate for typed configuration. The old Felix SCR annotations (org.apache.felix.scr.annotations) are deprecated and shouldn't appear in new code. See the annotations reference.
@Component(service = PriceService.class)
@Designate(ocd = PriceService.Config.class)
public class PriceService {
@ObjectClassDefinition(name = "Price Service")
public @interface Config {
@AttributeDefinition String endpoint() default "https://api.example.com";
@AttributeDefinition int timeoutMs() default 5000;
}
private Config config;
@Activate @Modified
protected void activate(Config config) { this.config = config; }
}33. Explain @Reference cardinality, policy, and policy option. (Senior)
Cardinality: MANDATORY (1..1, the default), OPTIONAL (0..1), MULTIPLE (0..n), AT_LEAST_ONE (1..n). Policy: STATIC (the default) means the component is deactivated and reactivated when the bound service changes. DYNAMIC means it's rebound in place, so your field must be volatile or the list thread-safe. Policy option: RELUCTANT (the default) keeps the current service even if a better one appears. GREEDY switches to a higher-ranked service as soon as one registers. A mandatory reference that can't be satisfied leaves the component unsatisfied, which is a very common "why is my service null or missing" cause.
34. What is service ranking? (Mid)
service.ranking is an integer service property that breaks ties. When a consumer binds a single service and several are available, the one with the highest ranking wins (default 0). When rankings are equal, the one with the lowest service ID (registered first) wins. Multiple-cardinality references receive services ordered by ranking. To override a default implementation, register yours with a higher ranking, and make sure consumers use GREEDY or get restarted, or they may keep the old binding.
35. Immediate vs delayed components? (Senior)
A component that provides a service is delayed by default: DS registers the service, but only creates the instance when someone first gets it. A component that provides no service is immediate, activated as soon as its dependencies are satisfied. @Component(immediate = true) forces activation of a service component, which you need for things like schedulers or listeners that must run even if nobody calls them. Knowing this explains "my @Activate never runs" bugs.
36. How do OSGi configurations work in AEM? (Mid)
Configurations are stored as files in the repository and delivered through the JCR installer to Configuration Admin, keyed by PID (usually the fully qualified class name). The modern format is .cfg.json, for example /apps/mysite/osgiconfig/config.publish.prod/com.mysite.core.PriceService.cfg.json. When several run-mode folders define the same PID, the one with the most matching run modes wins, and it applies to the whole PID, so you can't split one PID's properties across folders. configurationPolicy = REQUIRE means the component won't activate without a configuration. On AEMaaCS, configs can only come from Git through Cloud Manager, not the Web Console.
37. What is a factory configuration? (Mid)
A factory lets one component class run as many instances, one per configuration. Declare @Designate(ocd = ..., factory = true) and name files <factoryPID>~<name>.cfg.json, for example com.mysite.core.FeedImporter~news.cfg.json and ...~blog.cfg.json. Consumers typically take a MULTIPLE reference and pick an instance by a property. Classic uses: logger configs, service-user mappings (ServiceUserMapperImpl.amended~mysite.cfg.json), and per-endpoint clients.
38. What happens when a configuration changes? (Senior)
If the component has a @Modified method, DS calls it with the new configuration and the instance stays alive. Without one, DS deactivates and reactivates the component, which also rebinds its consumers if they're static. Deleting the configuration of a REQUIRE-policy component deactivates it. Use @Modified for cheap updates, but keep state rebuild logic correct either way.
39. How do you handle per-environment values and secrets? (Mid)
On AEMaaCS, reference Cloud Manager environment variables in OSGi configs: $[env:API_ENDPOINT;default=https://api.example.com] for normal values, $[secret:API_KEY] for secrets, which are never logged or exposed. Values are set per environment in Cloud Manager (UI, API, or aio CLI), up to 200 variables per environment, and names can't use reserved prefixes such as ADOBE_, INTERNAL_, or AEM_. On 6.5 there's no such feature, so teams use run-mode folders plus encrypted values (Crypto Support, {...}-wrapped ciphertext) or an external vault. See the Cloud Service guide.
JCR and Oak
40. Primary vs mixin node types, and common ones? (Junior)
Every node has exactly one primary type, which defines its allowed properties and children. Mixins add capabilities on top. Common primary types: nt:unstructured (anything goes, the default for component content), nt:folder, sling:Folder / sling:OrderedFolder, cq:Page with its cq:PageContent child jcr:content, dam:Asset, and nt:file / nt:resource for files. Common mixins: mix:versionable, mix:referenceable (gives jcr:uuid), mix:lockable, and rep:AccessControllable (the node has ACLs).
41. How does Oak store data? (Mid)
Oak separates the content model from storage via a NodeStore. SegmentNodeStore (TarMK) stores tar files on local disk. It's fast and single-instance. DocumentNodeStore stores nodes in MongoDB or an RDBMS and supports a clustered, shared repository. Binaries go to a separate blob/data store (file data store, S3, Azure Blob). On AEMaaCS all of this is managed for you. The detail to know is that author runs as a cluster, so code must be cluster-aware. See the JCR and Oak guide.
42. What is MVCC in Oak, and why does it matter to developers? (Senior)
Oak uses multi-version concurrency control: each session reads from a stable snapshot revision, so reads never block writes. The practical consequences: a long-lived session can see stale data until it refreshes, concurrent writes to the same node can fail at save() with a conflict (OakState0001-style InvalidItemStateException), and cluster nodes see each other's changes with a small delay. Keep sessions and resolvers short-lived, retry conflicting writes, and don't use the repository as a lock or counter under heavy concurrency.
43. Which query languages can you use? (Junior/Mid)
JCR-SQL2 (the standard), XPath (deprecated in the JCR 2.0 spec but still fully supported by Oak and common in AEM), and AEM's QueryBuilder, a predicate-based Java/HTTP API that generates XPath under the hood. All three go through the same Oak query engine and indexes, so the performance rules are identical. See the Query Builder reference.
SELECT * FROM [cq:PageContent] AS c
WHERE ISDESCENDANTNODE(c, '/content/mysite')
AND c.[cq:template] = '/conf/mysite/settings/wcm/templates/article'44. What index types does Oak support? (Mid)
Property indexes (synchronous, exact-match on a property; good for unique or rare values), Lucene indexes (asynchronous, full-text and property, sorting, and aggregation; the default choice), plus the counter and nodetype indexes the engine uses internally. On AEMaaCS, index management is supported only for lucene indexes. Adobe may serve some queries from internal Elasticsearch indexes, but customers don't configure those. Solr indexes are deprecated.
45. What is traversal, and why is it bad? (Mid)
If no index can answer a query, Oak traverses: it reads nodes one by one to find matches. It logs a warning for every 10,000 nodes traversed, and AEM enforces a read limit (100,000 nodes by default, queryLimitReads), after which the query is forcibly stopped with an exception. Traversal is slow, burns I/O, and gets worse as content grows, so a query that "works on my machine" fails in production. Fix it with an index, or by restricting the query so an existing index applies.
What interviewers look for: the difference between repository traversal in code (fine for a known, bounded set of children) and query traversal (always a bug).
46. How do you add or customise an index on AEMaaCS? (Senior)
Deploy the index definition through ui.apps under /oak:index (as _oak_index in the file system) using Adobe's naming convention: damAssetLucene-8-custom-1 to customise a product index (copy the latest product version and add your changes), or acme.product-1-custom-1 for a fully custom one. Bump the custom-N suffix for every change. During deployment, the new index is built before traffic switches, because the old and new code versions run side by side for a while. On 6.5 you can edit /oak:index directly and trigger a reindex, but deploying it as code is still best practice.
{
"jcr:primaryType": "oak:QueryIndexDefinition",
"type": "lucene", "async": ["async"], "compatVersion": 2,
"includedPaths": ["/content/mysite"], "queryPaths": ["/content/mysite"],
"evaluatePathRestrictions": true,
"indexRules": { "cq:PageContent": { "properties": {
"productId": { "name": "productId", "propertyIndex": true }
} } }
}47. How do you debug a slow query? (Senior)
First, find it: slow-query warnings in the logs, the Query Performance tool (Tools, then Diagnosis, on 6.5; the Developer Console's Queries tab on AEMaaCS), or Oak's JMX QueryStat. Then EXPLAIN it to see the plan: which index is used, or whether it traverses, and whether the restrictions and sort are handled by the index or filtered afterwards in memory. Fix it by narrowing the path, adding the property to an index rule, making sort properties ordered, or rewriting the query. Measure again afterwards.
48. What does p.guessTotal do in QueryBuilder? (Mid)
By default QueryBuilder counts all matching results to report total, which means iterating the full result set. p.guessTotal=true (or a number such as p.guessTotal=100) stops counting early and returns an estimate, which is dramatically cheaper for large result sets. Combine it with p.limit and p.offset for pagination, and avoid p.limit=-1 on unbounded queries. Adobe's query best-practices page calls this out explicitly.
49. When should you query vs navigate? (Mid)
Navigate (getChild, listChildren) when you know where the content is and the set is bounded, such as a component's multifield items or a page's direct children. Query when you need to search across an unknown or large subtree, and make sure the query is indexed. For components that render lists on every page view, avoid running the query at render time: precompute via a job or listener, or cache the result, as Adobe's guidance recommends.
Components, templates, and HTL
50. What makes up an AEM component? (Junior)
A component is a node under /apps (type cq:Component) with: a jcr:title and componentGroup (use .hidden to hide it from the component browser), an optional sling:resourceSuperType, an HTL script (for example title.html), a cq:dialog (the Granite UI authoring dialog), an optional cq:design_dialog (policy dialog), an optional cq:editConfig (editing behaviour), and usually a Sling Model in the core bundle. Walk through creating one, or scaffold one with the AEM Component generator.
51. cq:dialog vs cq:design_dialog? (Junior)
cq:dialog edits instance content. Values are stored on that component's node under the page. cq:design_dialog edits policies: settings shared by every instance of that component within a template's policy (allowed heading levels, available styles), stored under /conf/<site>/settings/wcm/policies. Rule of thumb: if the value differs per placement, it's a dialog field. If a template author should set it once, it's a policy.
52. What is cq:editConfig used for? (Mid)
It configures authoring behaviour in the page editor: cq:actions (which toolbar actions appear), cq:dropTargets (for example dragging an asset onto an image component), cq:inplaceEditing (inline rich-text editing), and cq:listeners (such as afteredit with REFRESH_PAGE, when a change affects more than the component itself). cq:childEditConfig applies the same settings to child components of a container.
53. Static vs editable templates? (Junior/Mid)
Static templates live under /apps, are defined by developers, and fix the page structure in code. Editable templates live under /conf/<site>/settings/wcm/templates and are built by template authors in the Template Editor, without a deployment. They have structure (locked components on every page), initial content (unlocked defaults that are copied into new pages), a responsive layout, and policies. Pages keep a live link to their editable template's structure, so structure changes propagate. Editable templates are the standard. Static templates are legacy.
54. What are content policies and the Style System? (Mid)
A content policy is a reusable configuration applied to a component within a template (or to the page itself), set through the design dialog. Several templates can share one policy. The Style System is part of the policy: developers define CSS classes, authors pick named styles from the component toolbar, and the chosen class is added to the component's wrapper. One component can then look different without new code. The Core Components and Style System guide covers it in depth.
55. What are Core Components and the proxy pattern? (Junior/Mid)
Core Components are Adobe's open-source, versioned, accessible, production-ready components (Title, Text, Image, Teaser, List, Container, Navigation, Form, and others) with built-in JSON export and data-layer support. You never reference them directly from content. Instead you create a proxy component under /apps/mysite/components whose sling:resourceSuperType points to a specific version (for example core/wcm/components/image/v3/image). Content references your resource type, so you control upgrades per component and can customise it without touching Adobe's code.
What interviewers look for: why a proxy matters: version pinning, safe upgrades, and a stable resource type in content. Also that on AEMaaCS Core Components ship with the product and are updated automatically, while on 6.5 you deploy them yourself.
56. Why HTL over JSP? (Junior)
HTL is secure by default: every expression is automatically escaped for the context it appears in, which makes XSS much harder to introduce. It also keeps logic out of markup, since anything complex belongs in a Sling Model, and HTML stays valid and readable for front-end developers. JSP gives you full Java in the template, and no automatic escaping. See the HTL cheat sheet.
57. Explain HTL display contexts. (Mid)
HTL chooses an escaping context from where the expression sits: text in element bodies, attribute in attributes, uri in href/src, and script or style contexts inside those blocks. You can set one explicitly with @ context=: html (filters markup through the XSS filter, for rich text), text, attribute, uri, number, scriptString, scriptToken, styleString, styleToken, comment, and unsafe (no escaping at all). Output in on* event-handler and style attributes is removed unless you give an explicit context, because it's inherently risky.
<div class="${properties.cssClass}">${properties.title}</div>
<div>${model.richText @ context='html'}</div>
<a href="${model.link}">Read more</a>What interviewers look for:
context='html'for rich text rather thanunsafe, and the understanding thatunsafeis almost never the right answer.
58. data-sly-use, data-sly-resource, data-sly-include, data-sly-template? (Mid)
data-sly-use instantiates a Sling Model (or a JS/Java use object) and exposes it as a variable. data-sly-resource renders another resource through Sling with its own resource type, which is how you include a child component, optionally forcing a resourceType. data-sly-include includes another script in the current resource's context, with no new Sling request for a different resource. data-sly-template and data-sly-call define and invoke reusable markup blocks, such as Java-free macros.
59. What are client libraries, and embed vs dependencies? (Mid)
A client library is a cq:ClientLibraryFolder with categories, and HTL includes it by category (via the clientlib templates in /libs/granite/sightly/templates/clientlib.html). dependencies makes the other category load as a separate file before yours. embed merges the other category's code into your file, which means fewer requests. Set allowProxy=true and serve from /etc.clientlibs/..., so the Dispatcher never needs to expose /apps. On AEMaaCS, strict versioning adds a content hash to the URL, so clientlibs can be cached long-term. See the front-end integration guide.
60. Content Fragments vs Experience Fragments? (Junior/Mid)
Content Fragments are structured, presentation-free data based on a Content Fragment Model (fields such as title, body, and references), stored as assets in the DAM, and ideal for headless delivery via GraphQL or APIs. Experience Fragments are designed, reusable groups of components: a page-like piece of layout (a header, a promo banner) with variations, reused across pages, and exportable to Adobe Target. Rule of thumb: CF is content without layout, XF is content with layout. The CF and XF guide goes deeper.
61. How would you design a multifield in a dialog? (Mid)
Use granite/ui/components/coral/foundation/form/multifield with composite="{Boolean}true", which stores each item as a child node (item0, item1, and so on) under the field's name, rather than as a single multi-value property. Read items in a Sling Model with @ChildResource(name = "links") List<Resource>, or a List of child models. Non-composite multifields only suit a single simple value per item, such as a list of tags.
62. How do you make a component work in both editor and publish? (Mid)
Render gracefully when empty. Core Components use a placeholder when nothing is authored (data-sly-test plus the core/wcm/components/commons/v1/templates.html placeholder template) and show nothing on publish. Don't rely on wcmmode for business logic, only for authoring affordances. Avoid JavaScript that breaks when the editor re-renders a component after an edit, or use cq:editConfig listeners to refresh.
Dispatcher and caching
63. What does the Dispatcher do? (Junior)
It's an Apache HTTP Server module with three jobs. Caching: it stores rendered responses as files in the docroot and serves them without hitting publish. Security: its filters allow or deny requests before they reach AEM. Load balancing: across publish renders, on 6.5 setups. The best-performing AEM sites serve most traffic from the CDN and Dispatcher, and publish renders only on cache misses. See the complete Dispatcher guide.
64. What does the Dispatcher not cache? (Mid)
Per the Dispatcher docs: requests with a query string (unless the parameters are listed in /ignoreUrlParams), requests without a file extension, non-GET/HEAD methods, responses that aren't HTTP 200, and requests with authentication headers or cookies unless /allowAuthorized is enabled. It also skips responses the /rules section excludes, and responses carrying Dispatcher: no-cache. Knowing these explains most "why isn't this cached?" tickets.
65. How does invalidation work? (Mid/Senior)
There are two mechanisms. Deletion: a flush request for a path deletes that exact file (and on deactivation, its subtree). Auto-invalidation: the Dispatcher touches .stat files, and any cached file matching the /invalidate rules (typically *.html) whose timestamp is older than its nearest .stat file is treated as stale and re-fetched on the next request. Auto-invalidation is why activating one page also refreshes the navigation on every other cached page in that branch. It doesn't delete them, it just makes them stale.
66. What does /statfileslevel actually control? (Senior)
The Dispatcher keeps .stat files at each folder level from the docroot (level 0) down to statfileslevel. On activation, it touches the .stat files from the docroot down to the level of the invalidated path, or to statfileslevel, whichever is smaller. A cached file checks the .stat in its nearest folder. Example with statfileslevel "2" (/content is level 1, /content/site-a is level 2): activating a page under /content/site-a/en touches /.stat, /content/.stat, and /content/site-a/.stat. Pages under /content/site-a go stale, but /content/site-b/.stat is untouched, so site B's cache survives.
With 0 (the default), there's a single .stat at the root, so every activation invalidates every auto-invalidatable file on the host.
What interviewers look for: the precise semantics, not "how many levels to flush". Architects should connect the value to the content structure, usually the level of site or language roots in multi-site setups.
67. Where should the Dispatcher flush agent live? (Senior)
On AEM 6.5, the recommended setup is flush agents on the publish instances (triggered on receive), not on author. Flushing from author can race with replication: the Dispatcher might re-fetch the page from a publish instance that hasn't received the new version yet and re-cache the old content. On AEMaaCS you don't configure flush agents. Publishing invalidates the Dispatcher automatically, and for custom invalidation Adobe recommends Sling Content Distribution from author (deduplicated and guaranteed) over the best-effort Replication API from publish.
68. How should Dispatcher filters be written? (Mid)
Deny everything first, then allow what's needed: specific paths, extensions, selectors, and methods. When several rules match, the last matching rule wins, so order matters. Explicitly keep blocked: consoles (/system/console, /crx/*), /bin/querybuilder.json, JSON dumps (.infinity.json, .tidy.json, numeric depth selectors), .sysview.xml and .docview.xml, and any selector you don't use. Use /url, /extension, /selectors, /path, and /method properties rather than one big /glob regex. Test with the Dispatcher tester.
/filter {
/0001 { /type "deny" /url "*" }
/0100 { /type "allow" /method "GET" /path "/content/*" /extension '(html|json|jpg|png|svg|webp)' }
/0110 { /type "deny" /selectors '(feed|rss|pages|languages|blueprint|infinity|tidy|sysview|docview|query|[0-9-]+)' /extension '(json|xml|html)' }
}69. How do you cache pages that use query parameters? (Mid)
Add /ignoreUrlParams: parameters marked ignore (for example utm_*, gclid, fbclid) are treated as if absent, so the request is served from and stored into the cache under the parameter-free path. Only ignore parameters that don't change the response. If a parameter changes output (a search term, a filter), either don't cache that response or move the value into a selector or suffix so each variant gets its own cache file.
70. What are gracePeriod, serveStaleOnError, and enableTTL? (Senior)
/gracePeriod is the number of seconds a stale, auto-invalidated file can still be served after the last activation, which smooths out bursts of activations. /serveStaleOnError keeps and serves invalidated content if publish returns an error (502, 503, 504), which is a cheap resilience win. /enableTTL makes the Dispatcher honour Cache-Control/Expires headers from the backend, so cached files expire on their own even without a flush. That's useful for content you can't invalidate by path, such as external data.
71. What is permission-sensitive caching? (Senior)
For content behind login (for example a CUG-protected area), the Dispatcher can cache it and still check authorisation. With /auth_checker configured, before serving a cached file it sends a HEAD request to a servlet you write, which returns 200 if the current user can read the path and 403 otherwise. You get cache speed with per-user access control. It suits content shared by groups of users. Truly per-user content shouldn't be cached this way.
72. How is the Dispatcher different on AEMaaCS? (Mid)
The Dispatcher is part of every publish instance, and its config lives in the dispatcher module of your Git repository, deployed by the full-stack or web-tier config pipeline. Some files are immutable (Adobe-provided defaults you can't override), and the Dispatcher SDK validator checks your config locally and in the pipeline. Flexible mode (opt-in/USE_SOURCES_DIRECTLY) is the current standard. There's also a CDN in front, and by default HTML gets a 5-minute Cache-Control from Apache, which the CDN also respects. You tune this with EXPIRATION_TIME or mod_headers.
73. A published page still shows old content. How do you debug it? (Mid/Senior)
Work from the outside in. Browser: hard reload, and check the response headers (Age, Cache-Control, and the CDN's cache status headers). CDN: is it serving within its TTL? If so, wait or purge. Dispatcher: is the file in the docroot older than its .stat? Did invalidation happen (check the Dispatcher logs and statfileslevel)? Is the page's extension covered by /invalidate? Publish: request the page directly. Did replication actually arrive (check the distribution/replication status)? Only then suspect the code. See the performance and troubleshooting guide.
Workflows and Assets
74. What are models, instances, launchers, and payloads? (Junior)
A workflow model is the design (steps plus transitions), edited in /conf/global/settings/workflow/models and synced to a runtime copy under /var/workflow/models. An instance is one running execution. The payload is what it runs on, usually a JCR path. A launcher starts a model automatically when content matching its node type, path, and condition is created, modified, or removed. A work item is the active step. For human steps it lands in an Inbox. See the workflows guide.
75. How do you write a custom process step? (Mid)
Implement com.adobe.granite.workflow.exec.WorkflowProcess as an OSGi component with a process.label property, so it appears in the Process Step dropdown. In execute(WorkItem, WorkflowSession, MetaDataMap), check the payload type, get the path, adapt the workflow session to a ResourceResolver, and do the work. Throw WorkflowException on failure so the step fails visibly and can retry.
@Component(service = WorkflowProcess.class,
property = "process.label=MySite: Tag Page")
public class TagPageProcess implements WorkflowProcess {
@Override
public void execute(WorkItem item, WorkflowSession session, MetaDataMap args)
throws WorkflowException {
if (!"JCR_PATH".equals(item.getWorkflowData().getPayloadType())) return;
String path = item.getWorkflowData().getPayload().toString();
ResourceResolver rr = session.adaptTo(ResourceResolver.class);
// ... modify content, then rr.commit()
}
}76. Participant vs dynamic participant step? (Mid)
A participant step assigns the work item to a fixed user or group chosen in the model. A dynamic participant step calls a ParticipantStepChooser you implement, which returns the assignee at runtime, for example the approver group for the payload's site or region. Use dynamic participants instead of cloning models per team.
77. What is a transient workflow? (Mid)
A workflow model marked transient doesn't persist its instance history to the repository, which means much less write load and no purge needed. That's ideal for high-volume automation with no audit requirement. It can't include steps that need persistence, like participant steps waiting for a human.
78. Workflow vs Sling Job vs event listener? (Senior)
Use a workflow when authors need visibility, approvals, or a model they can change (human-in-the-loop business processes). Use a Sling Job for reliable background processing with no human involvement: guaranteed, retried, and cheaper. Use a ResourceChangeListener (or an OSGi event handler) to react to changes, then do the heavy lifting in a job, because listeners must return quickly. Launching a workflow just to run code is an anti-pattern, since you pay for workflow persistence you don't need.
79. How do you prevent workflow launcher loops? (Mid)
If a workflow modifies the content that triggered its launcher, it re-launches itself, over and over. Scope launchers tightly (path glob, node type, condition), use the launcher's exclude list to skip changes made by the workflow's own service user or specific properties, and avoid writing to the watched node where possible. Monitor the instance count in the Workflow console after deploying a new launcher.
80. Why purge workflow instances? (Mid)
Completed instances stay in the repository under /var/workflow/instances until purged. Millions of them bloat the repository and slow the workflow engine and queries. Configure the Workflow Purge maintenance task (by model, status, and age), and use transient workflows where history isn't needed. On AEMaaCS, purge-related maintenance settings are managed through the config pipeline for the tasks Adobe exposes.
81. How does asset processing work on AEMaaCS? (Mid)
Binaries are uploaded directly to cloud blob storage (direct binary upload) instead of streaming through AEM, and renditions and metadata extraction are done by asset microservices (Asset Compute) that scale elastically. The DAM Update Asset workflow's rendition steps are replaced. You configure extra renditions with processing profiles on folders, and custom code runs in post-processing workflows, applied via folder properties or the Custom Workflow Runner service. Those workflows must end with the DAM Update Asset Workflow Completed Process step. On 6.5, the DAM Update Asset workflow does everything in-process. See the Assets guide.
82. What are renditions and how should front-end code use them? (Junior/Mid)
Renditions are derived versions of an asset (thumbnails, web-optimised sizes), stored under the asset's jcr:content/renditions. The original is the original rendition. Pages should never serve the original to browsers. Use the Core Image component's adaptive widths or web-optimised delivery, or Dynamic Media, so the browser gets a right-sized, modern format. On AEMaaCS, Adobe recommends streaming binaries through the CDN rather than serving large binaries from AEM.
Headless and hybrid
83. What are Content Fragment Models? (Junior)
A Content Fragment Model defines a structured content type: fields (single-line text, multi-line or rich text, number, boolean, date, enumeration, tags, content and fragment references, JSON), validation, and required flags. Models live in /conf/<site>/settings/dam/cfm/models, and must be enabled on a configuration that the DAM folder uses. Each model automatically generates GraphQL types. Use the CF Model builder to sketch one.
84. Full-stack vs headless vs hybrid? (Mid)
Full-stack: AEM renders the HTML with components and HTL. Headless: AEM stores and delivers structured content (Content Fragments) as JSON through GraphQL or the OpenAPI-based APIs, and a separate front end (React, Next.js, mobile) renders it. Hybrid: AEM pages are delivered both ways. Page JSON via .model.json, and in-context editing of the front end via the Universal Editor or, on older builds, the SPA Editor. Choose based on who controls rendering and how many channels consume the content.
85. How does AEM's GraphQL API work? (Mid)
Enable GraphQL on a /conf configuration, and create an endpoint (for example /content/cq:graphql/mysite/endpoint.json). AEM generates a schema from your CF Models, with query fields like articleList, articleByPath, and paginated variants, supporting filtering, sorting, and nested references. Queries can be explored in GraphiQL on author. The API is read-only (delivery), so fragments are created and updated through the Assets HTTP API or the Content Fragment OpenAPIs. See the APIs and integrations guide.
86. Why use persisted queries? (Mid/Senior)
A persisted query is stored on the server (created with PUT /graphql/persist.json/<config>/<name>) and executed with a GET to /graphql/execute.json/<config>/<name>, with variables appended as ;var=value (URL-encoded). Because it's a GET with a stable URL, the response is cacheable at the Dispatcher and CDN. POSTed ad-hoc GraphQL isn't. Default cache headers on publish are max-age=60 and s-maxage=7200, and you can tune them per query. Adobe recommends persisted queries for production. Ad-hoc POST queries should be limited to development.
What interviewers look for: caching as the reason, plus security (clients can't run arbitrary expensive queries) and allowing only
/graphql/execute.json/*through the Dispatcher.
87. What else can deliver Content Fragments on AEMaaCS? (Senior)
Besides GraphQL, AEMaaCS offers OpenAPI-based APIs: Content Fragment Delivery with OpenAPI for optimised, CDN-integrated JSON delivery with active invalidation, and the Sites API for Content Fragment and Model management (CRUD), a modern alternative to the older Assets HTTP API for fragments. Delivery with OpenAPI currently has to be enabled per environment through Adobe. Check the current docs before relying on specific endpoints, as this area is evolving quickly.
88. What is the status of the SPA Editor? (Mid)
Adobe deprecated the SPA Editor in AEM 6.5.23 and AEMaaCS release 2025.01. Existing implementations remain supported (P1/P2 fixes and security issues), but it gets no new features, and Adobe recommends the Universal Editor for all new projects. The Universal Editor instruments any front end (any framework, SSR or client-side) with data-aue-* attributes and edits content in AEM, whether that's Content Fragments or pages.
89. How do you allow a headless app on another domain to call AEM? (Mid)
Configure CORS on publish with the com.adobe.granite.cors.impl.CORSPolicyImpl factory configuration: allowed origins, paths (for example /graphql/execute.json/.*), methods, and headers. Make sure the Dispatcher passes the Origin header and doesn't cache one origin's CORS headers for another. For authenticated headless access on AEMaaCS, use technical accounts or OAuth server-to-server credentials, never shared user passwords.
AEM as a Cloud Service
90. What fundamentally changed with AEMaaCS? (Mid)
AEMaaCS is a cloud-native, auto-scaling, continuously updated service (Adobe applies AEM updates automatically; there are no more service packs). /apps and /libs are immutable, and code and OSGi configs arrive only through Cloud Manager pipelines. Replication agents are replaced by Sling Content Distribution. Asset processing moves to microservices. There's an Adobe-managed CDN in front. Custom run modes, Classic UI, and reverse replication are gone. See the Cloud Service guide.
91. What pipeline types does Cloud Manager have? (Mid)
Full-stack (Java, ui.apps, content, and Dispatcher config), front-end (a front-end build for site themes, which can run independently), web tier config (Dispatcher/Apache config only, which deploys much faster), and config pipelines (CDN rules, traffic filters and WAF, log forwarding, and purge maintenance tasks). Pipelines are production (stage, then prod, with approval) or non-production (dev, or code-quality only). There can be only one full-stack pipeline per environment at a time.
92. What quality gates does a production pipeline enforce? (Mid)
Code quality uses three severity levels. Critical issues fail the pipeline: a Security Rating below B, or a Reliability Rating below D. Important issues pause it for an override: a Maintainability Rating below A, or coverage below 50%. Info issues are reported only. Production pipelines also run security tests, performance tests on stage, product and custom functional tests, UI tests, and an Experience Audit (Lighthouse-based). Skipping tests to get green isn't a strategy, and interviewers know it.
93. What is a Rapid Development Environment? (Mid)
An RDE is a special cloud environment (author plus publish) where you deploy bundles, content packages, OSGi configs, Dispatcher config (flexible mode), and front-end code in seconds with the aio CLI (aio aem:rde:install ./all/target/mysite.all.zip), with no pipeline. It's for fast validation against real cloud behaviour, not performance testing, and it's intended for one developer at a time. It uses the rde run mode. Once a change works there, commit it and let the pipeline carry it forward.
94. How do you develop locally for AEMaaCS? (Junior/Mid)
Use the AEM SDK: the Quickstart JAR (the same codebase as the cloud service, updated regularly), the Dispatcher SDK tools (run the Dispatcher in Docker and validate config), and matching Java and Maven versions. Start author with java -jar aem-sdk-quickstart.jar -r author, or with -r publish,dev, deploy with mvn clean install -PautoInstallSinglePackage, and keep the SDK version close to your cloud environment's. See the local development setup guide.
95. What developer tools replace server access on AEMaaCS? (Mid)
The Developer Console (per environment) gives status dumps of bundles, components, configurations, Oak indexes, and Sling jobs, plus query analysis. The Repository Browser gives a read-only view of any tier. CRXDE Lite is available only on development environments' author, not stage or prod. Logs can be downloaded from Cloud Manager, tailed with the aio CLI, or forwarded to Splunk, Datadog, and similar tools through log forwarding. Log levels are set per environment via run-mode OSGi configs, and DEBUG shouldn't be enabled in production.
96. What coding rules does AEMaaCS impose? (Senior)
Adobe's development guidelines: code always runs in a cluster, and instances can stop at any time, so keep no state in memory or on the local disk, which is ephemeral. Use Sling Jobs rather than the Scheduler for work that must happen. Set timeouts on outgoing HTTP calls (Adobe suggests about 1 second to connect and 5 seconds to read; the platform caps them at 10 seconds and 60 seconds). Don't use native binaries, Classic UI customisations, or reverse replication. Stream binaries through the CDN rather than AEM.
What interviewers look for: that you design for failure, with idempotent jobs, retries, and timeouts, not just that you know the list.
97. How is the CDN configured on AEMaaCS? (Senior)
Through YAML files (kind: "CDN", version: "1") in a config folder, deployed by the config pipeline. You can declare request and response transformations, origin selectors (routing paths to other backends, such as Edge Delivery or an API), redirects at the edge, and traffic filter rules: allow and block rules, rate limits, and, with the extra security license, WAF rules. You can also bring your own CDN in front of Adobe's. Cache TTLs are driven by the Cache-Control headers AEM and the Dispatcher emit, so fix caching at the source first.
98. What is advanced networking? (Senior)
By default, outbound traffic from AEMaaCS uses shared IPs and standard HTTP/HTTPS ports. Advanced networking is configured per program in Cloud Manager and offers flexible port egress (non-standard ports, such as SMTP or databases), dedicated egress IP (a stable IP that partners can allowlist), and VPN (to on-premise systems). Code reaches ported services via the AEM_PROXY_HOST environment variable. This matters whenever an integration requires IP allowlisting.
99. How does a deployment achieve zero downtime? (Architect)
Cloud Manager builds a new image, creates new indexes first, and does a rolling (blue-green style) deployment: new pods start alongside the old ones, and traffic shifts once they're healthy. For a while, old and new code run at the same time against the same repository, so changes must be backwards compatible. Don't rename a property the old code still reads in the same release, or remove a node old code depends on. Use expand-and-contract migrations across two releases.
Security
100. What is a service user, and how do you use it? (Mid)
A service user (system user) is a non-login principal with only the permissions a piece of code needs. Create it and its ACLs with repoinit (in ui.config), map your bundle's sub-service to it with a ServiceUserMapperImpl.amended factory config, and open a resolver with resolverFactory.getServiceResourceResolver(Map.of(ResourceResolverFactory.SUBSERVICE, "reader")) inside try-with-resources. loginAdministrative and getAdministrativeResourceResolver are deprecated, and using them is a security and audit red flag.
create service user mysite-reader with path system/cq:services/mysite
set ACL for mysite-reader
allow jcr:read on /content/mysite
end{ "user.mapping": ["com.mysite.core:reader=[mysite-reader]"] }101. How does CSRF protection work in AEM? (Mid)
The Granite CSRF Filter requires a valid token for authenticated POST, PUT, and DELETE requests on both author and publish. GET and anonymous requests are exempt. Clients fetch a short-lived token from /libs/granite/csrf/token.json and send it in the CSRF-Token header (or :cq_csrf_token for forms). The granite.csrf.standalone clientlib automates this. Missing or invalid tokens cause a 403 with "empty CSRF token - rejecting" in the log. Remember to allow the token endpoint in the Dispatcher.
102. How are ACLs evaluated? (Senior)
Oak evaluates permission entries in this order: user principals before group principals, regardless of order; entries on the target node before inherited ones; and within the same level, the later entry in the list overrides earlier ones. There's no blanket "deny always wins". An inherited deny on /content is overridden by an allow on /content/public. Design with groups rather than individual users, grant at the highest sensible level, and use deny sparingly. The security guide goes deeper.
What interviewers look for: correcting the "deny wins" myth, and knowing that user-level entries beat group-level ones.
103. What is a Closed User Group? (Mid)
A CUG restricts read access to a content tree on publish to specified principals (stored as a rep:CugPolicy with rep:principalNames). It's usually combined with an authentication requirement (the granite:AuthenticationRequired mixin with an optional granite:loginPath), so anonymous visitors are redirected to log in. By default, CUG evaluation is enabled on publish only. Authors can manage CUGs on author without being restricted by them. Authors set it via page properties, then Permissions. Protected pages then need permission-sensitive caching or no caching at the Dispatcher.
104. How do you secure a custom servlet? (Senior)
Bind it to a resource type so repository ACLs apply (see question 23), use SlingSafeMethodsServlet if it's read-only, validate and encode every input and output (XSSAPI for HTML contexts), never build queries by concatenating user input, rely on CSRF protection for authenticated writes, and allow only the exact selector, extension, and method in the Dispatcher filters. Don't return stack traces or internal paths in errors, and use a service user with minimal rights if it needs elevated access.
105. How do users authenticate on AEMaaCS? (Mid)
Author users sign in through Adobe IMS (Adobe ID or federated enterprise SSO via the Admin Console). Access is granted through product profiles that map to AEM groups. Publish (site visitors) supports SAML 2.0 via the SAML authentication handler, and other mechanisms like OAuth or custom handlers, for gated content. Technical integrations use service credentials (OAuth server-to-server or technical account tokens). On 6.5, you manage local users, LDAP sync, or SAML yourself.
106. What does a Dispatcher security review cover? (Mid)
Deny-by-default filters, blocked admin paths and JSON dumps, no /crx or /system/console exposure, restricted selectors and extensions, CSRF token endpoint allowed only as needed, /allowAuthorized off unless required, security headers (CSP, HSTS, X-Content-Type-Options, X-Frame-Options, or frame-ancestors) added at Apache or the CDN, and query strings controlled. Check headers with the security headers tool and read the web security headers guide.
Performance and troubleshooting
107. How do you analyse a hung or slow instance? (Senior)
Take several thread dumps 5–10 seconds apart (jstack -l <pid> or jcmd <pid> Thread.print) and compare them. Threads stuck in the same frame across dumps are your suspects: blocked on a lock, waiting on an outgoing HTTP call with no timeout, or iterating a traversing query. Look at request threads, which show the URL in the thread name, and BLOCKED states. On AEMaaCS you don't have shell access, so rely on logs, the Developer Console, and Adobe Support.
108. How do you investigate an OutOfMemoryError? (Senior)
Make sure -XX:+HeapDumpOnOutOfMemoryError is set, or capture a heap dump with jcmd <pid> GC.heap_dump file.hprof. Open it in Eclipse MAT, and check the dominator tree and leak suspects. Common AEM culprits: unclosed ResourceResolvers and sessions, unbounded caches in static maps, huge query result sets loaded into memory, and large binaries read into byte arrays. Correlate with GC logs to tell a leak (steadily rising old gen) from a spike (a burst of large allocations).
109. What is a resource resolver leak? (Mid)
Every ResourceResolver you open (service or otherwise) holds a JCR session. If you don't close it, it stays open, and the leaks accumulate until memory runs out. The signs: "unclosed ResourceResolver" warnings in error.log with the stack trace of where it was opened, a growing session count in JMX, and SessionImpl objects piling up in heap dumps. The fix is try-with-resources for every resolver you open. Never close the request's own resolver, since Sling owns it.
110. A page is slow on publish. Walk me through it. (Senior)
First, confirm it's actually rendering on publish (a cache miss) and not a CDN or Dispatcher issue. Then use request progress tracking (the Recent Requests console on 6.5 or the SDK) to see time per component and include. Check the logs for slow-query and traversal warnings. Look for components doing queries, external HTTP calls, or deep traversal at render time. Fix by caching (Dispatcher, or in-memory with invalidation), moving work to jobs, or adding indexes. Then fix the cache hit ratio, which usually helps more than the render time does.
111. How do you raise the cache hit ratio? (Senior)
Make more responses cacheable: fewer query strings (/ignoreUrlParams for tracking parameters), selectors instead of parameters for variants, and no personalised data in cached HTML. Make invalidation narrower with a sensible statfileslevel, and avoid full-tree activations. Set good Cache-Control TTLs at the CDN, with stale-while-revalidate and stale-if-error. Serve personalised fragments separately, and use serveStaleOnError. Measure hit ratio from CDN logs, not guesses.
112. A component's @Reference service is null or the component isn't active. Why? (Mid)
Check the component's state in the Components console or the Developer Console. Unsatisfied means a mandatory reference or a required configuration is missing. Bundle not active usually means an unresolved import. A field that's "null" often turns out to be a class instantiated with new instead of being looked up as a service, or a Sling Model using @Reference instead of @OSGiService, since @Reference only works in DS components. adaptTo returning null means a required injection failed, and ModelFactory will tell you which.
Architecture and scenarios
113. Design a multi-brand, multi-language site platform. (Architect)
Structure /content/<brand>/<country>/<language> with MSM: a language master (blueprint) and live copies per market, with rollout configs deciding what syncs automatically and what local teams can detach. Use language copies and translation projects (with a translation connector) for localisation. Separate brand configuration with CA Config and /conf/<brand> for templates, policies, and CF Models, share one codebase and component library with the Style System for brand variation, and set permissions per brand and market via groups. Line up Dispatcher statfileslevel and vanity or host mapping with the tree. See the MSM and translation guide.
What interviewers look for: whether you'd use MSM at all (it adds complexity; not every multi-site needs live copies), and how you'd handle local overrides and rollout governance.
114. How do you cache a site with personalised content? (Architect)
Keep the page shell cacheable for everyone, and deliver the personalised parts separately. Options: client-side calls to an uncached (or privately cached) JSON endpoint, or Adobe Target and Experience Platform decisions at the edge or in the browser. Server-side includes: Sling Dynamic Include (an open-source module) turns a component into an include (SSI in Apache, or ESI where your CDN supports it), so the web tier assembles the page. Verify SSI or ESI support in your specific Dispatcher and CDN setup first. Segment-based variants: cache a small number of variants keyed by a selector or header rather than per user. Never let a cached response contain one user's data, and set Cache-Control: private on anything user-specific. See the Analytics, Target, and data layer guide.
115. How would you plan a 6.5 to Cloud Service migration? (Architect)
Assess: run the Best Practices Analyzer on 6.5 and review the findings in Cloud Acceleration Manager (custom run modes, /libs overlays, replication agents, Classic UI, custom index definitions, static templates, and asset workflows). Refactor code: restructure into mutable and immutable packages (Repository Modernizer), convert OSGi configs, convert Dispatcher config to the cloud format, and replace in-process asset workflows with processing profiles and post-processing. Move content with the Content Transfer Tool (extraction and ingestion, then top-up runs for deltas before cutover). Validate in stage with performance tests, freeze content, do a final top-up, and cut over with DNS/CDN. See the migration guide.
116. How would you integrate a commerce platform? (Architect)
For Adobe Commerce, the Commerce Integration Framework (CIF) add-on connects AEM to Commerce's GraphQL API, with CIF Core Components for product and category pages. Product data stays in Commerce, and pages render it at request time or client-side, with marketing content authored in AEM. For other platforms, integrate via their APIs: server-side via OSGi services with caching and timeouts, or client-side for cart and price. Keep price and stock out of cached HTML (fetch them client-side or with short TTLs), use catalog-driven page templates rather than one page per product, and decide who owns SEO URLs.
117. How would you integrate an external search engine? (Architect)
Choose push or pull. Push: on publish events, send content to the search index. On AEMaaCS, subscribe to AEM Eventing (via Adobe I/O Events) from an external consumer, or use an in-AEM replication or distribution event handler that enqueues a Sling Job, which then calls the search API with retries. Pull: the search vendor crawls the site or a sitemap. Index structured data (from Content Fragments or page JSON), not scraped HTML where possible, handle unpublish and deletes, respect ACLs or CUGs for gated content, and plan a full re-index path. Query from the front end via the vendor's API, not through AEM, so search traffic doesn't hit publish.
118. Traditional, headless, or Edge Delivery Services? (Architect)
Traditional full-stack AEM suits content-heavy sites with rich in-context authoring and an existing component library. Headless suits multi-channel content (apps, kiosks, a custom React or Next.js front end), where the front-end team owns rendering and hosting. Edge Delivery Services delivers pages from the edge with a very lightweight front end (blocks in plain JavaScript and CSS), authored in documents or the Universal Editor, and suits marketing sites where Core Web Vitals and speed of delivery matter. Decide on authoring experience, team skills, performance targets, and integration needs, not fashion. See the Edge Delivery Services guide and Next.js for AEM developers.
Quick-fire round
Short answers interviewers often use as warm-ups.
| Question | Answer |
|---|---|
| Default Sling search path order? | /apps first, then /libs |
| Final fallback resource super type? | sling/servlet/default |
| Selector for Sling Model Exporter JSON? | model (for example .model.json) |
Does adaptTo throw on failure? | No, it returns null. ModelFactory throws |
Default @Reference cardinality and policy? | Mandatory (1..1), static, reluctant |
Higher or lower service.ranking wins? | Higher |
| Factory config file separator? | ~ (for example Pid~name.cfg.json) |
| Custom run modes on AEMaaCS? | Not possible |
| Dispatcher: which filter rule wins? | The last matching rule |
| Cached if the URL has a query string? | No, unless the parameters are in /ignoreUrlParams |
| Where do editable templates live? | /conf/<site>/settings/wcm/templates |
| HTL context for rich text? | html |
| Where are CF Models stored? | /conf/<site>/settings/dam/cfm/models |
| Persisted query execution path? | /graphql/execute.json/<config>/<name> |
| Oak query read limit in AEM? | 100,000 nodes by default |
| Index type customers manage on AEMaaCS? | lucene only |
| Tool to create service users as code? | Repoinit |
| CSRF token header name? | CSRF-Token |
| Replacement for replication agents on AEMaaCS? | Sling Content Distribution |
| Last step of a post-processing workflow? | DAM Update Asset Workflow Completed Process |
| SPA Editor status? | Deprecated. Use the Universal Editor for new work |
How to prepare
Reading answers gets you through the first ten minutes. What gets you hired is being able to talk from experience, so build things.
- Do the WKND tutorial end to end. Adobe's "Getting Started with AEM Sites" (WKND) tutorial takes you through the project archetype, components, Sling Models, editable templates, the Style System, and clientlibs. Then do the WKND Headless tutorial for Content Fragments, GraphQL, and persisted queries. Both are on Experience League, and the reference code is on GitHub.
- Run AEM locally. Install the AEM SDK author and publish instances and the Dispatcher tools in Docker, following the local setup guide. Break the Dispatcher config on purpose and watch the validator catch it.
- Build a small project that touches every layer:
- A proxy of a Core Component, customised through delegation.
- A custom component with a composite multifield and a Sling Model with unit tests (AEM Mocks, above 80% coverage).
- A resource-type servlet that returns JSON, filtered correctly in the Dispatcher.
- An OSGi factory configuration, with a value coming from an environment variable.
- A Sling Job triggered by a
ResourceChangeListener. - A custom workflow process step with a launcher, and a launcher-loop guard.
- A Content Fragment Model, a persisted query, and a tiny Next.js page consuming it.
- A custom Lucene index for one of your queries, with an EXPLAIN plan before and after.
- Practise explaining request flow. Draw the path of a request from browser to CDN to Dispatcher to publish to Sling to HTL and back, and the path of an activation from author to publish to invalidation. Most architect interviews start with a whiteboard version of this. The architecture guide has a template.
- Read the release notes. AEMaaCS changes every month. Skim the last few releases so you can talk about what's new (Universal Editor, OpenAPI-based APIs, AEM Eventing, Edge Delivery Services) without guessing.
- Use the tools. Try the Sling Resolver Simulator, Dispatcher tester, package filter builder, and query reports to check your mental model against real behaviour.
- Prepare your stories. For senior and architect roles, prepare two or three real incidents (a performance issue, a cache bug, a migration) using situation, action, and result. Interviewers weigh what you've actually handled more heavily than trivia.
Some interview-blog answers are simply outdated: path-bound servlets as the default, Felix SCR annotations, admin resolvers, "deny always wins", and statfileslevel as "levels to flush". If a source contradicts the official docs, trust the docs, and say so in the interview. Knowing why the old answer changed is itself a senior-level signal.
Wrapping up
AEM interviews reward understanding over recall. If you can trace a request through Sling resolution, explain how OSGi wires and configures your code, reason about Oak queries and indexes, describe precisely how the Dispatcher caches and invalidates, and say what's different on AEM as a Cloud Service, you can answer almost any question here, even ones phrased differently. At senior and architect level, add the tradeoffs: what to cache, where to integrate, and how to migrate without downtime.
Go deeper on each area with the full guides: Sling, OSGi, JCR and Oak, component development, HTL, Dispatcher, workflows, Assets, Cloud Service, security, and performance and troubleshooting. For the bigger picture of where to go next, see the AEM Developer Roadmap. Good luck with the interview.
Discussion
Loading discussion…
Try a related tool
Subscribe to the Newsletter
Get the latest articles, tutorials, and tech insights delivered straight to your inbox. No spam, unsubscribe anytime.

