Summary
On a vanilla Apache Sling 12+ instance (i.e. one that does not ship Adobe Granite bundles), installing accesscontroltool-bundle 4.1.1 leaves every AC Tool component unable to instantiate. SCR reports each component as satisfied, but every getService() call returns null, so:
/system/console/actool returns HTTP 404 (the Felix Web Console plugin cannot bind AcToolUiService).
AcInstallationService (and its AcInstallationServiceInternal/JobExecutor facets) is never delivered.
- The Touch UI servlet at
/mnt/overlay/netcentric/actool/content/overview.html cannot resolve.
- In short: the tool is installed but unusable, even though the bundle is
Active.
This contradicts the README, which states:
It is also possible to run the AC Tool on Apache Sling 12 or above (ensure system user actool-service has jcr:all permissions on root). When using the AC Tool with Sling, actions in ACE definitions and encrypted passwords cannot be used. […]
and the explicit comment in accesscontroltool-bundle/bnd.bnd:
# allow to run in Sling without AEM bundles
# allow to run without bouncycastle which is only necessary for some edge cases when managing keys
Import-Package: \
com.adobe.granite.crypto;resolution:=optional,\
com.adobe.granite.keystore;resolution:=optional,\
…
The optional imports let the bundle resolve, but they do not protect it from the JVM's eager resolution of field types during reflection (see "Root cause" below).
Environment
- AC Tool:
accesscontroltool-bundle 4.1.1 (latest release as of writing)
- Container: vanilla Apache Sling 12+ on Apache Felix, with Apache Felix SCR 2.2.18
- JVM: OpenJDK 25.0.3 (also reproducible on JDK 21)
- No bundle in the framework exports
com.adobe.granite.keystore or com.adobe.granite.crypto
Reproduction
- Start a vanilla Apache Sling 12+ instance.
- Drop
accesscontroltool-bundle-4.1.1.jar into <sling>/launcher/startup (or install it via the Web Console). Do not install any Adobe Granite bundles.
- After the bundle reaches state
Active, observe error.log for entries like the ones in "Observed log output" below.
- Visit
/system/console/components. All AC Tool components show satisfied, none ever become active.
- Visit
/system/console/actool → HTTP 404.
- Issue any call that consumes
AcInstallationService (for example via the AC Tool MBean or a programmatic bundleContext.getService(...)). The ServiceFactory returns null.
The behavior is fully deterministic — stopping and starting the AC Tool bundle re-produces the same sequence of errors.
Observed log output
Excerpt from error.log (timestamps from a single bundle start; full stack trace abbreviated):
*ERROR* AuthorizableInstallerServiceImpl … : Field externalGroupCreatorService cannot be
found in component class biz.netcentric.cq.tools.actool.authorizableinstaller.impl.
AuthorizableInstallerServiceImpl. The field will be ignored.
(org.apache.felix.log.LogException: java.lang.NoClassDefFoundError:
com/adobe/granite/keystore/KeyStoreService)
org.apache.felix.log.LogException: java.lang.NoClassDefFoundError:
com/adobe/granite/keystore/KeyStoreService
at java.base/java.lang.Class.getDeclaredFields0(Native Method)
at java.base/java.lang.Class.privateGetDeclaredFields(Class.java:2917)
at java.base/java.lang.Class.getDeclaredField(Class.java:2380)
at org.apache.felix.scr.impl.inject.field.FieldUtils.getField(FieldUtils.java:157)
at org.apache.felix.scr.impl.inject.field.FieldUtils.searchField(FieldUtils.java:87)
at org.apache.felix.scr.impl.inject.field.FieldHandler$NotResolved.resolve(FieldHandler.java:388)
at org.apache.felix.scr.impl.inject.field.FieldHandler$NotResolved.fieldExists(FieldHandler.java:406)
at org.apache.felix.scr.impl.inject.field.FieldHandler.fieldExists(FieldHandler.java:463)
at org.apache.felix.scr.impl.inject.field.FieldHandler$ReferenceMethodImpl.getServiceObject(FieldHandler.java:545)
…
*ERROR* AuthorizableInstallerServiceImpl … : Field [decryptionService] not found; Component will fail
*ERROR* AuthorizableInstallerServiceImpl … : Field [externalGroupCreatorService] not found; Component will fail
*ERROR* AuthorizableInstallerServiceImpl … : Field [impersonationInstallerService] not found; Component will fail
*ERROR* AuthorizableInstallerServiceImpl … : Field [repository] not found; Component will fail
*ERROR* AuthorizableInstallerServiceImpl … : Field [resourceResolverFactory] not found; Component will fail
*ERROR* FrameworkEvent ERROR (Service factory returned null. (Component:
biz.netcentric.cq.tools.actool.authorizableinstaller.impl.AuthorizableInstallerServiceImpl))
*WARN* AcInstallationServiceImpl … : Could not get service from ref
[biz.netcentric.cq.tools.actool.authorizableinstaller.AuthorizableInstallerService]
*ERROR* Service factory returned null. (Component: AcInstallationServiceImpl)
*WARN* AcToolUiService … : Could not get service from ref
[AcInstallationService, AcInstallationServiceInternal, JobExecutor]
*ERROR* Service factory returned null. (Component: AcToolUiService)
*WARN* AcToolWebconsolePlugin … : Could not get service from ref [AcToolUiService]
*ERROR* Service factory returned null. (Component: AcToolWebconsolePlugin)
Root cause
AuthorizableInstallerServiceImpl declares an optional, dynamic reference to com.adobe.granite.keystore.KeyStoreService as a typed field
(source, line 108):
import com.adobe.granite.keystore.KeyStoreNotInitialisedException; // line 64
import com.adobe.granite.keystore.KeyStoreService; // line 65
…
@Reference(cardinality = ReferenceCardinality.OPTIONAL,
policy = ReferencePolicy.DYNAMIC,
policyOption = ReferencePolicyOption.GREEDY)
volatile KeyStoreService keyStoreService; // line 108
OSGi resolution is fine — the bundle wires up with the import unbound because com.adobe.granite.keystore is declared resolution:=optional in bnd.bnd.
The problem arises later, when Felix SCR introspects the class to bind references. Apache Felix SCR FieldUtils.searchField calls Class.getDeclaredField(name), which the JVM implements via the native Class.getDeclaredFields0(boolean publicOnly). That native method walks the constant pool of the class, eagerly resolving the declared type of every field — including the KeyStoreService field. Because no bundle in a vanilla Sling distribution exports com.adobe.granite.keystore, the bundle's classloader cannot resolve Lcom/adobe/granite/keystore/KeyStoreService; and throws NoClassDefFoundError.
Apache Felix SCR catches the failure and marks the field as NotResolved in its FieldHandler cache. From that point on, every subsequent lookup against any field of the class returns "not found" (FieldHandler$NotResolved.fieldExists → false), regardless of whether that field's own type is resolvable. The same AuthorizableInstallerServiceImpl therefore reports Field [decryptionService] not found; Component will fail, Field [repository] not found; Component will fail, etc., even though decryptionService, repository, and resourceResolverFactory are perfectly ordinary Sling types.
Once AuthorizableInstallerServiceImpl cannot be instantiated, every downstream consumer cascades:
AuthorizableInstallerServiceImpl (null)
└── AcInstallationServiceImpl (null — @Reference AuthorizableInstallerService)
└── AcToolUiService (null — @Reference AcInstallationServiceInternal)
├── AcToolTouchUiServlet
└── AcToolWebconsolePlugin → /system/console/actool returns 404
The components show as satisfied only because SCR's reference-cardinality check is independent of class loading — the optional 0..1 keyStoreService reference is correctly satisfied by "0 providers", and all other references find matching services. The failure mode is purely on the class-introspection path.
Why CI does not catch this
The CI matrix in .github/workflows/maven.yml resolves the bundle against target-osgi-environment/minimum-environment/minimum-aem.bndrun, which pulls in AEM/Granite bundles via com.adobe.aem:uber-jar. In that resolution context, com.adobe.granite.keystore is exported by com.adobe.granite.crypto.api (or equivalent), the optional import wires up, and field-type resolution succeeds. There appears to be no equivalent minimum-sling.bndrun exercising a true vanilla Sling 12 distribution, so the regression is invisible to the matrix.
Suggested fixes (alternatives)
Pick one — any of these resolves the symptom:
-
Ship a small accesscontroltool-sling-shim bundle alongside accesscontroltool-bundle (or as an optional artifact). It exports com.adobe.granite.keystore and com.adobe.granite.crypto packages containing stub types (KeyStoreService, KeyStoreNotInitialisedException, CryptoSupport, CryptoException) whose method bodies are unreachable on Sling. Users would deploy this on Sling but not on AEM.
-
Remove the typed field from AuthorizableInstallerServiceImpl and replace it with a bind/unbind method whose parameter type is hidden behind an interface that lives inside the AC Tool bundle. The Adobe Granite type would only be referenced from inside the method body, where lazy resolution applies. Example:
@Reference(cardinality = OPTIONAL, policy = DYNAMIC,
policyOption = GREEDY,
service = Object.class, // erased type
target = "(objectClass=com.adobe.granite.keystore.KeyStoreService)")
private volatile Object keyStoreService; // no granite types in class signature
(or use bind/unbind methods that take ServiceReference<?> and call KeyStoreService only from inside method bodies that are protected with try { … } catch (NoClassDefFoundError) { … } if needed).
-
Move the keystore feature into a separate "AEM-only" sub-bundle so the core AC Tool bundle has no compile-time reference to com.adobe.granite.*.
-
Add a minimum-sling.bndrun to the CI matrix that exercises a real vanilla Sling 12 environment, so this class of failure cannot reach a release again, and document any required shims in docs/Installation.md.
Workaround for downstream users
Until upstream chooses a fix, users on vanilla Sling can deploy a tiny "stub" OSGi bundle that exports the missing Granite packages, e.g.:
// com/adobe/granite/keystore/KeyStoreService.java
package com.adobe.granite.keystore;
public interface KeyStoreService { /* never invoked on Sling */ }
// com/adobe/granite/keystore/KeyStoreNotInitialisedException.java
package com.adobe.granite.keystore;
public class KeyStoreNotInitialisedException extends Exception { }
…packaged as an OSGi bundle that exports com.adobe.granite.keystore (and the analogous classes for com.adobe.granite.crypto). Because the AC Tool's optional imports are then wired, the JVM can resolve the field types, SCR introspection succeeds, all AC Tool components become active, and the tool is fully functional — including /system/console/actool and the install hook.
This works because the relevant @Reference cardinality is 0..1: SCR will never actually bind a KeyStoreService instance (none are registered), so no method on the stub is ever invoked.
Summary
On a vanilla Apache Sling 12+ instance (i.e. one that does not ship Adobe Granite bundles), installing
accesscontroltool-bundle4.1.1 leaves every AC Tool component unable to instantiate. SCR reports each component assatisfied, but everygetService()call returnsnull, so:/system/console/actoolreturns HTTP 404 (the Felix Web Console plugin cannot bindAcToolUiService).AcInstallationService(and itsAcInstallationServiceInternal/JobExecutorfacets) is never delivered./mnt/overlay/netcentric/actool/content/overview.htmlcannot resolve.Active.This contradicts the README, which states:
and the explicit comment in
accesscontroltool-bundle/bnd.bnd:The optional imports let the bundle resolve, but they do not protect it from the JVM's eager resolution of field types during reflection (see "Root cause" below).
Environment
accesscontroltool-bundle4.1.1 (latest release as of writing)com.adobe.granite.keystoreorcom.adobe.granite.cryptoReproduction
accesscontroltool-bundle-4.1.1.jarinto<sling>/launcher/startup(or install it via the Web Console). Do not install any Adobe Granite bundles.Active, observeerror.logfor entries like the ones in "Observed log output" below./system/console/components. All AC Tool components showsatisfied, none ever becomeactive./system/console/actool→ HTTP 404.AcInstallationService(for example via the AC Tool MBean or a programmaticbundleContext.getService(...)). TheServiceFactoryreturnsnull.The behavior is fully deterministic — stopping and starting the AC Tool bundle re-produces the same sequence of errors.
Observed log output
Excerpt from
error.log(timestamps from a single bundle start; full stack trace abbreviated):Root cause
AuthorizableInstallerServiceImpldeclares an optional, dynamic reference tocom.adobe.granite.keystore.KeyStoreServiceas a typed field(source, line 108):
OSGi resolution is fine — the bundle wires up with the import unbound because
com.adobe.granite.keystoreis declaredresolution:=optionalinbnd.bnd.The problem arises later, when Felix SCR introspects the class to bind references. Apache Felix SCR
FieldUtils.searchFieldcallsClass.getDeclaredField(name), which the JVM implements via the nativeClass.getDeclaredFields0(boolean publicOnly). That native method walks the constant pool of the class, eagerly resolving the declared type of every field — including theKeyStoreServicefield. Because no bundle in a vanilla Sling distribution exportscom.adobe.granite.keystore, the bundle's classloader cannot resolveLcom/adobe/granite/keystore/KeyStoreService;and throwsNoClassDefFoundError.Apache Felix SCR catches the failure and marks the field as
NotResolvedin itsFieldHandlercache. From that point on, every subsequent lookup against any field of the class returns "not found" (FieldHandler$NotResolved.fieldExists→ false), regardless of whether that field's own type is resolvable. The sameAuthorizableInstallerServiceImpltherefore reportsField [decryptionService] not found; Component will fail,Field [repository] not found; Component will fail, etc., even thoughdecryptionService,repository, andresourceResolverFactoryare perfectly ordinary Sling types.Once
AuthorizableInstallerServiceImplcannot be instantiated, every downstream consumer cascades:The components show as
satisfiedonly because SCR's reference-cardinality check is independent of class loading — the optional0..1keyStoreServicereference is correctly satisfied by "0 providers", and all other references find matching services. The failure mode is purely on the class-introspection path.Why CI does not catch this
The CI matrix in
.github/workflows/maven.ymlresolves the bundle againsttarget-osgi-environment/minimum-environment/minimum-aem.bndrun, which pulls in AEM/Granite bundles viacom.adobe.aem:uber-jar. In that resolution context,com.adobe.granite.keystoreis exported bycom.adobe.granite.crypto.api(or equivalent), the optional import wires up, and field-type resolution succeeds. There appears to be no equivalentminimum-sling.bndrunexercising a true vanilla Sling 12 distribution, so the regression is invisible to the matrix.Suggested fixes (alternatives)
Pick one — any of these resolves the symptom:
Ship a small
accesscontroltool-sling-shimbundle alongsideaccesscontroltool-bundle(or as an optional artifact). It exportscom.adobe.granite.keystoreandcom.adobe.granite.cryptopackages containing stub types (KeyStoreService,KeyStoreNotInitialisedException,CryptoSupport,CryptoException) whose method bodies are unreachable on Sling. Users would deploy this on Sling but not on AEM.Remove the typed field from
AuthorizableInstallerServiceImpland replace it with a bind/unbind method whose parameter type is hidden behind an interface that lives inside the AC Tool bundle. The Adobe Granite type would only be referenced from inside the method body, where lazy resolution applies. Example:(or use
bind/unbindmethods that takeServiceReference<?>and callKeyStoreServiceonly from inside method bodies that are protected withtry { … } catch (NoClassDefFoundError) { … }if needed).Move the keystore feature into a separate "AEM-only" sub-bundle so the core AC Tool bundle has no compile-time reference to
com.adobe.granite.*.Add a
minimum-sling.bndrunto the CI matrix that exercises a real vanilla Sling 12 environment, so this class of failure cannot reach a release again, and document any required shims indocs/Installation.md.Workaround for downstream users
Until upstream chooses a fix, users on vanilla Sling can deploy a tiny "stub" OSGi bundle that exports the missing Granite packages, e.g.:
…packaged as an OSGi bundle that exports
com.adobe.granite.keystore(and the analogous classes forcom.adobe.granite.crypto). Because the AC Tool's optional imports are then wired, the JVM can resolve the field types, SCR introspection succeeds, all AC Tool components becomeactive, and the tool is fully functional — including/system/console/actooland the install hook.This works because the relevant
@Referencecardinality is0..1: SCR will never actually bind aKeyStoreServiceinstance (none are registered), so no method on the stub is ever invoked.