diff --git a/pom.xml b/pom.xml
index 068b8f5..1e898d2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,6 +37,7 @@
qqq-all-bom
+ qqq-all-bom-test
qqq-all-app
diff --git a/qqq-all-bom-test/pom.xml b/qqq-all-bom-test/pom.xml
new file mode 100644
index 0000000..6b0b939
--- /dev/null
+++ b/qqq-all-bom-test/pom.xml
@@ -0,0 +1,254 @@
+
+
+
+
+ 4.0.0
+
+
+ com.kingsrook.qqq
+ qqq-all-parent
+ ${revision}
+
+
+ qqq-all-bom-test
+ jar
+
+ QQQ All - Stack BOM Test
+ Resolves every artifact the stack BOM manages. Build-time check only; not published.
+
+
+ true
+
+
+
+
+
+ com.kingsrook.qqq
+ qqq-all-bom
+ ${project.version}
+ pom
+ import
+
+
+
+
+
+
+
+
+
+ com.kingsrook.qqq
+ qqq-backend-core
+ test
+
+
+ com.kingsrook.qqq
+ qqq-backend-module-rdbms
+ test
+
+
+ com.kingsrook.qqq
+ qqq-backend-module-mongodb
+ test
+
+
+ com.kingsrook.qqq
+ qqq-backend-module-api
+ test
+
+
+ com.kingsrook.qqq
+ qqq-backend-module-filesystem
+ test
+
+
+ com.kingsrook.qqq
+ qqq-backend-module-sqlite
+ test
+
+
+ com.kingsrook.qqq
+ qqq-backend-module-postgres
+ test
+
+
+ com.kingsrook.qqq
+ qqq-middleware-javalin
+ test
+
+
+ com.kingsrook.qqq
+ qqq-middleware-slack
+ test
+
+
+ com.kingsrook.qqq
+ qqq-middleware-api
+ test
+
+
+ com.kingsrook.qqq
+ qqq-openapi
+ test
+
+
+ com.kingsrook.qqq
+ qqq-middleware-picocli
+ test
+
+
+ com.kingsrook.qqq
+ qqq-middleware-lambda
+ test
+
+
+ com.kingsrook.qqq
+ qqq-middleware-health
+ test
+
+
+ com.kingsrook.qqq
+ qqq-language-support-javascript
+ test
+
+
+ com.kingsrook.qqq
+ qqq-utility-lambdas
+ test
+
+
+ com.kingsrook.qqq
+ qqq-esb
+ test
+
+
+ com.kingsrook.qqq
+ qqq-frontend-next
+ test
+
+
+
+
+ com.kingsrook.qbits
+ qbit-quick-search
+ test
+
+
+ com.kingsrook.qbits
+ qbit-user-role-permissions
+ test
+
+
+ com.kingsrook.qbits
+ qbit-customizable-table-views
+ test
+
+
+ com.kingsrook.qbits
+ qbit-standard-process-trace
+ test
+
+
+ com.kingsrook.qbits
+ qbit-webhooks
+ test
+
+
+ com.kingsrook.qbits
+ qbit-workflows
+ test
+
+
+ com.kingsrook.qbits
+ qbit-geo-data
+ test
+
+
+ com.kingsrook.qbits
+ qbit-sftp-data-integration
+ test
+
+
+
+
+ org.postgresql
+ postgresql
+ test
+
+
+ com.mysql
+ mysql-connector-j
+ test
+
+
+ org.xerial
+ sqlite-jdbc
+ test
+
+
+ com.h2database
+ h2
+ test
+
+
+
+
+ org.apache.artemis
+ artemis-jakarta-client
+ test
+
+
+ com.rabbitmq.jms
+ rabbitmq-jms
+ test
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 6.0.2
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-help-plugin
+ 3.5.2
+
+
+ effective-pom
+ generate-test-resources
+
+ effective-pom
+
+
+
+
+
+
+
+
+
+
+
diff --git a/qqq-all-bom-test/src/test/java/com/kingsrook/qqq/all/bom/StackBomTest.java b/qqq-all-bom-test/src/test/java/com/kingsrook/qqq/all/bom/StackBomTest.java
new file mode 100644
index 0000000..273e31c
--- /dev/null
+++ b/qqq-all-bom-test/src/test/java/com/kingsrook/qqq/all/bom/StackBomTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2026 QRunIO, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.kingsrook.qqq.all.bom;
+
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+/*******************************************************************************
+ ** Checks qqq-all-bom through this module's effective POM, which Maven writes
+ ** with the BOM imports expanded: the BOM manages the whole stack, and this
+ ** module depends on every stack artifact the BOM manages, so the build
+ ** resolves each one.
+ *******************************************************************************/
+class StackBomTest
+{
+ private static final Path EFFECTIVE_POM = Path.of("target", "effective-pom.xml");
+ private static final Path BOM_POM = Path.of("..", "qqq-all-bom", "pom.xml");
+
+ private static List managed;
+ private static List dependencies;
+ private static List bomDeclared;
+
+
+
+ /*******************************************************************************
+ ** Read the managed and declared dependencies from the effective POM.
+ *******************************************************************************/
+ @BeforeAll
+ static void beforeAll() throws Exception
+ {
+ Element effectivePom = parse(EFFECTIVE_POM);
+ managed = readDependencies(child(effectivePom, "dependencyManagement"));
+ dependencies = readDependencies(effectivePom);
+
+ bomDeclared = readDependencies(child(parse(BOM_POM), "dependencyManagement")).stream()
+ .filter(coordinate -> !"import".equals(coordinate.scope()))
+ .toList();
+ }
+
+
+
+ /*******************************************************************************
+ ** The BOM manages every artifact the qqq-all stack is built from.
+ *******************************************************************************/
+ @Test
+ void testManagesTheStack()
+ {
+ Map managedVersions = managed.stream().collect(Collectors.toMap(Coordinate::key, Coordinate::version));
+
+ List required = List.of(
+ "com.kingsrook.qqq:qqq-backend-core",
+ "com.kingsrook.qqq:qqq-esb",
+ "com.kingsrook.qqq:qqq-frontend-next",
+ "com.kingsrook.qbits:qbit-quick-search",
+ "com.kingsrook.qbits:qbit-user-role-permissions",
+ "com.kingsrook.qbits:qbit-customizable-table-views",
+ "com.kingsrook.qbits:qbit-standard-process-trace",
+ "com.kingsrook.qbits:qbit-webhooks",
+ "com.kingsrook.qbits:qbit-workflows",
+ "com.kingsrook.qbits:qbit-geo-data",
+ "com.kingsrook.qbits:qbit-sftp-data-integration",
+ "org.postgresql:postgresql",
+ "com.mysql:mysql-connector-j",
+ "org.xerial:sqlite-jdbc",
+ "com.h2database:h2",
+ "org.apache.artemis:artemis-jakarta-client",
+ "com.rabbitmq.jms:rabbitmq-jms");
+
+ List missing = required.stream().filter(key -> !managedVersions.containsKey(key)).toList();
+ assertEquals(List.of(), missing, "Artifacts the stack BOM does not manage");
+ assertEquals("3.9.0", managedVersions.get("com.rabbitmq.jms:rabbitmq-jms"));
+ }
+
+
+
+ /*******************************************************************************
+ ** This module depends on every stack artifact the BOM manages, so resolving
+ ** its test classpath resolves each one from the configured repositories.
+ **
+ ** Stack artifacts are the managed com.kingsrook artifacts (qqq modules,
+ ** qqq-esb, qqq-frontend-next, qBits) plus everything qqq-all-bom declares
+ ** itself. The third-party entries qqq-bom-pom inherits from its parent
+ ** (Jackson, Netty, Jetty, and others) only align versions, so they are
+ ** resolved when something depends on them rather than here.
+ *******************************************************************************/
+ @Test
+ void testDependsOnEveryStackArtifact()
+ {
+ Set stack = managed.stream()
+ .filter(coordinate -> coordinate.groupId().startsWith("com.kingsrook."))
+ .map(Coordinate::key)
+ .collect(Collectors.toCollection(TreeSet::new));
+ bomDeclared.forEach(coordinate -> stack.add(coordinate.key()));
+
+ Set declared = dependencies.stream().map(Coordinate::key).collect(Collectors.toSet());
+
+ List notResolved = stack.stream().filter(key -> !declared.contains(key)).toList();
+ assertEquals(List.of(), notResolved, "Stack artifacts missing from qqq-all-bom-test's dependencies, so the build does not resolve them");
+ }
+
+
+
+ /*******************************************************************************
+ ** Parse a POM file and return its project element.
+ *******************************************************************************/
+ private static Element parse(Path pom) throws Exception
+ {
+ Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(pom.toFile());
+ return (document.getDocumentElement());
+ }
+
+
+
+ /*******************************************************************************
+ ** Read the dependency elements directly under parent's dependencies element.
+ *******************************************************************************/
+ private static List readDependencies(Element parent)
+ {
+ List coordinates = new ArrayList<>();
+ for(Element dependency : children(child(parent, "dependencies"), "dependency"))
+ {
+ coordinates.add(new Coordinate(
+ text(dependency, "groupId", null),
+ text(dependency, "artifactId", null),
+ text(dependency, "version", null),
+ text(dependency, "type", "jar"),
+ text(dependency, "classifier", ""),
+ text(dependency, "scope", "compile")));
+ }
+ return (coordinates);
+ }
+
+
+
+ /*******************************************************************************
+ ** The single child element with the given name.
+ *******************************************************************************/
+ private static Element child(Element parent, String name)
+ {
+ List matches = children(parent, name);
+ assertEquals(1, matches.size(), "Expected one <" + name + "> under <" + parent.getTagName() + ">");
+ return (matches.get(0));
+ }
+
+
+
+ /*******************************************************************************
+ ** The child elements with the given name (direct children only, so plugin
+ ** dependencies are not mistaken for project dependencies).
+ *******************************************************************************/
+ private static List children(Element parent, String name)
+ {
+ List matches = new ArrayList<>();
+ for(Node node = parent.getFirstChild(); node != null; node = node.getNextSibling())
+ {
+ if(node instanceof Element element && element.getTagName().equals(name))
+ {
+ matches.add(element);
+ }
+ }
+ return (matches);
+ }
+
+
+
+ /*******************************************************************************
+ ** Text of the named child element, or defaultValue when it is absent.
+ *******************************************************************************/
+ private static String text(Element parent, String name, String defaultValue)
+ {
+ List matches = children(parent, name);
+ return (matches.isEmpty() ? defaultValue : matches.get(0).getTextContent().trim());
+ }
+
+
+
+ /*******************************************************************************
+ ** A dependency coordinate; key() is how Maven matches a dependency to its
+ ** managed entry.
+ *******************************************************************************/
+ private record Coordinate(String groupId, String artifactId, String version, String type, String classifier, String scope)
+ {
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ String key()
+ {
+ String key = groupId + ":" + artifactId;
+ if(!type.equals("jar") || !classifier.isEmpty())
+ {
+ key += ":" + type + (classifier.isEmpty() ? "" : ":" + classifier);
+ }
+ return (key);
+ }
+ }
+
+}
diff --git a/qqq-all-bom/pom.xml b/qqq-all-bom/pom.xml
index 1bbcd4d..64552ca 100644
--- a/qqq-all-bom/pom.xml
+++ b/qqq-all-bom/pom.xml
@@ -16,4 +16,115 @@
QQQ All - Stack BOM
One tested version set for QQQ, the ESB, the Next dashboard, the included qBits, JDBC drivers, and broker clients.
+
+ 4.1.0-SNAPSHOT
+
+
+ 0.2.1-SNAPSHOT
+ 0.33.0-SNAPSHOT
+ 0.5.0-SNAPSHOT
+ 0.3.0-SNAPSHOT
+ 0.4.0-SNAPSHOT
+ 0.114.0-SNAPSHOT
+ 0.2.0-SNAPSHOT
+ 0.4.0-SNAPSHOT
+
+
+ 42.7.12
+ 9.5.0
+ 3.51.1.0
+ 2.4.240
+ 2.57.0
+ 3.9.0
+
+
+
+
+
+
+ com.kingsrook.qqq
+ qqq-bom-pom
+ ${qqq.version}
+ pom
+ import
+
+
+
+
+ com.kingsrook.qbits
+ qbit-quick-search
+ ${qbit-quick-search.version}
+
+
+ com.kingsrook.qbits
+ qbit-user-role-permissions
+ ${qbit-user-role-permissions.version}
+
+
+ com.kingsrook.qbits
+ qbit-customizable-table-views
+ ${qbit-customizable-table-views.version}
+
+
+ com.kingsrook.qbits
+ qbit-standard-process-trace
+ ${qbit-standard-process-trace.version}
+
+
+ com.kingsrook.qbits
+ qbit-webhooks
+ ${qbit-webhooks.version}
+
+
+ com.kingsrook.qbits
+ qbit-workflows
+ ${qbit-workflows.version}
+
+
+ com.kingsrook.qbits
+ qbit-geo-data
+ ${qbit-geo-data.version}
+
+
+ com.kingsrook.qbits
+ qbit-sftp-data-integration
+ ${qbit-sftp-data-integration.version}
+
+
+
+
+ org.postgresql
+ postgresql
+ ${postgresql.version}
+
+
+ com.mysql
+ mysql-connector-j
+ ${mysql-connector-j.version}
+
+
+ org.xerial
+ sqlite-jdbc
+ ${sqlite-jdbc.version}
+
+
+ com.h2database
+ h2
+ ${h2.version}
+
+
+
+
+ org.apache.artemis
+ artemis-jakarta-client
+ ${artemis.version}
+
+
+ com.rabbitmq.jms
+ rabbitmq-jms
+ ${rabbitmq-jms.version}
+
+
+
+