beansList) {
+ this.dependingBeans = beansList;
+ return this;
+ }
+
+ public Builder profiles(@Nullable String profiles) {
+ this.profiles = profiles;
+ return this;
+ }
+
+ public Builder primary() {
+ this.isPrimary = true;
+ return this;
+ }
+
+ public BeanDefinitionHolder build() {
+ BeanDefinitionHolder holder = new BeanDefinitionHolder(type, module, beanPackage, location);
+ holder.setDependingBeans(List.copyOf(dependingBeans));
+ holder.setProfiles(profiles);
+ if (isPrimary) {
+ holder.setPrimary();
+ }
+ return holder;
+ }
+ }
+}
diff --git a/java-frontend/src/main/java/org/sonar/java/model/springcontext/BeanDefinitionRegistry.java b/java-frontend/src/main/java/org/sonar/java/model/springcontext/BeanDefinitionRegistry.java
new file mode 100644
index 00000000000..8de01d51202
--- /dev/null
+++ b/java-frontend/src/main/java/org/sonar/java/model/springcontext/BeanDefinitionRegistry.java
@@ -0,0 +1,48 @@
+/*
+ * SonarQube Java
+ * Copyright (C) SonarSource Sàrl
+ * mailto:info AT sonarsource DOT com
+ *
+ * You can redistribute and/or modify this program under the terms of
+ * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonar.java.model.springcontext;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tracks {@link BeanDefinitionHolder bean definitions} collected during Spring context scanning,
+ * indexed by bean name.
+ *
+ * Multiple definitions registered under the same name are preserved rather than overwritten,
+ * allowing callers to detect and report duplicate bean declarations.
+ *
+ * @see BeanDefinitionHolder
+ */
+public class BeanDefinitionRegistry {
+ /**
+ * Maps bean names to their corresponding list of {@link BeanDefinitionHolder} instances.
+ * A list is used as the value to capture duplicate bean definitions under the same name,
+ * which is an invalid state that should be reported during analysis.
+ */
+ private final Map> beanDefinitions = new HashMap<>();
+
+ public List getByName(String beanName) {
+ return beanDefinitions.getOrDefault(beanName, List.of());
+ }
+
+ public void addBeanDefinition(String beanName, BeanDefinitionHolder beanDefinition) {
+ beanDefinitions.computeIfAbsent(beanName, k -> new ArrayList<>()).add(beanDefinition);
+ }
+}
diff --git a/java-frontend/src/main/java/org/sonar/java/model/springcontext/BeanLocation.java b/java-frontend/src/main/java/org/sonar/java/model/springcontext/BeanLocation.java
new file mode 100644
index 00000000000..34e71de346b
--- /dev/null
+++ b/java-frontend/src/main/java/org/sonar/java/model/springcontext/BeanLocation.java
@@ -0,0 +1,29 @@
+/*
+ * SonarQube Java
+ * Copyright (C) SonarSource Sàrl
+ * mailto:info AT sonarsource DOT com
+ *
+ * You can redistribute and/or modify this program under the terms of
+ * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonar.java.model.springcontext;
+
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.java.reporting.AnalyzerMessage;
+
+/**
+ * Source location of a Spring bean definition within the project.
+ *
+ * @param inputFile the file in which the bean is declared
+ * @param mainLocation the precise text span of the bean declaration, used for reporting issues
+ */
+public record BeanLocation(InputFile inputFile, AnalyzerMessage.TextSpan mainLocation) {
+}
diff --git a/java-frontend/src/main/java/org/sonar/java/model/springcontext/EntityClassToPropertiesIndex.java b/java-frontend/src/main/java/org/sonar/java/model/springcontext/EntityClassToPropertiesIndex.java
new file mode 100644
index 00000000000..7f9e4091fde
--- /dev/null
+++ b/java-frontend/src/main/java/org/sonar/java/model/springcontext/EntityClassToPropertiesIndex.java
@@ -0,0 +1,56 @@
+/*
+ * SonarQube Java
+ * Copyright (C) SonarSource Sàrl
+ * mailto:info AT sonarsource DOT com
+ *
+ * You can redistribute and/or modify this program under the terms of
+ * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonar.java.model.springcontext;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Index mapping JPA / Hibernate {@code @Entity} class names to their associated
+ * key-value properties collected during scanning.
+ *
+ * Multiple properties can be registered for the same entity class.
+ * Lookup returns an empty set for entity classes that have no registered properties.
+ */
+public class EntityClassToPropertiesIndex {
+ /** Properties indexed by fully-qualified {@code @Entity} class name. */
+ private final Map>> propertiesByEntityClass = new HashMap<>();
+
+ /**
+ * Registers a property for the given {@code @Entity} class.
+ *
+ * @param entityClass fully-qualified name of the {@code @Entity} class
+ * @param propertyKey the property name
+ * @param propertyValue the property value
+ */
+ public void addProperty(String entityClass, String propertyKey, String propertyValue) {
+ propertiesByEntityClass.computeIfAbsent(entityClass, k -> new HashSet<>()).add(Map.entry(propertyKey, propertyValue));
+ }
+
+ /**
+ * Returns an immutable set of all properties registered for the given {@code @Entity} class.
+ *
+ * @param entityClass fully-qualified name of the {@code @Entity} class
+ * @return an unmodifiable set of key-value property entries, or an empty set if none were registered
+ */
+ public Set> getPropertiesForEntity(String entityClass) {
+ return Collections.unmodifiableSet(propertiesByEntityClass.getOrDefault(entityClass, Set.of()));
+ }
+}
\ No newline at end of file
diff --git a/java-frontend/src/main/java/org/sonar/java/model/springcontext/ProjectPackageScan.java b/java-frontend/src/main/java/org/sonar/java/model/springcontext/ProjectPackageScan.java
new file mode 100644
index 00000000000..1bfd45958ec
--- /dev/null
+++ b/java-frontend/src/main/java/org/sonar/java/model/springcontext/ProjectPackageScan.java
@@ -0,0 +1,62 @@
+/*
+ * SonarQube Java
+ * Copyright (C) SonarSource Sàrl
+ * mailto:info AT sonarsource DOT com
+ *
+ * You can redistribute and/or modify this program under the terms of
+ * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonar.java.model.springcontext;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tracks the packages registered for Spring component scanning, grouped by module.
+ *
+ * Corresponds to packages declared via {@code @ComponentScan} (or equivalent) and
+ * collected during project analysis. Each module may declare multiple scanned packages.
+ */
+public class ProjectPackageScan {
+ /** Scanned package names indexed by module name. */
+ private final Map> packagesScannedBySpringPerModule = new HashMap<>();
+
+ /**
+ * Registers a package as scanned by Spring for the given module.
+ *
+ * @param module the module in which the component scan is configured
+ * @param packageName the package name declared for scanning
+ */
+ public void addPackage(String module, String packageName) {
+ packagesScannedBySpringPerModule.computeIfAbsent(module, k -> new HashSet<>()).add(packageName);
+ }
+
+ /**
+ * Returns the set of packages scanned by Spring for the given module.
+ *
+ * @param module the module name
+ * @return the scanned package names, or an empty set if none were registered
+ */
+ public Set getPackagesForModule(String module) {
+ return packagesScannedBySpringPerModule.getOrDefault(module, Set.of());
+ }
+
+ /**
+ * Returns all modules that have at least one registered scanned package.
+ *
+ * @return the set of module names
+ */
+ public Set getModules() {
+ return packagesScannedBySpringPerModule.keySet();
+ }
+}
\ No newline at end of file
diff --git a/java-frontend/src/main/java/org/sonar/java/model/springcontext/SpringContextModel.java b/java-frontend/src/main/java/org/sonar/java/model/springcontext/SpringContextModel.java
new file mode 100644
index 00000000000..d9ee0e64cce
--- /dev/null
+++ b/java-frontend/src/main/java/org/sonar/java/model/springcontext/SpringContextModel.java
@@ -0,0 +1,75 @@
+/*
+ * SonarQube Java
+ * Copyright (C) SonarSource Sàrl
+ * mailto:info AT sonarsource DOT com
+ *
+ * You can redistribute and/or modify this program under the terms of
+ * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonar.java.model.springcontext;
+
+/**
+ * Aggregates all Spring context information collected during project scanning.
+ *
+ * Acts as the top-level model passed to rules that need to reason about the Spring
+ * application context. Each field is a specialized index populated during the scan phase:
+ *
+ * - {@link BeanDefinitionRegistry} — bean definitions indexed by bean name
+ * - {@link ProjectPackageScan} — packages registered for component scanning, per module
+ * - {@link TypeToBeanNamesIndex} — bean names indexed by type
+ * - {@link EntityClassToPropertiesIndex} — JPA {@code @Entity} class properties
+ *
+ */
+public class SpringContextModel {
+ /** Registry of all bean definitions discovered during scanning. */
+ private BeanDefinitionRegistry beanDefinitionRegistry;
+
+ /** Packages registered for Spring component scanning, grouped by module. */
+ private ProjectPackageScan projectPackageScan;
+
+ /** Index for resolving bean names by their fully-qualified type. */
+ private TypeToBeanNamesIndex typeToBeanNamesIndex;
+
+ /** Index of properties associated with Spring Data / Hibernate {@code @Entity} classes. */
+ private EntityClassToPropertiesIndex entityClassToPropertiesIndex;
+
+ public BeanDefinitionRegistry getBeanDefinitionRegistry() {
+ return beanDefinitionRegistry;
+ }
+
+ public void setBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) {
+ this.beanDefinitionRegistry = beanDefinitionRegistry;
+ }
+
+ public ProjectPackageScan getProjectPackageScan() {
+ return projectPackageScan;
+ }
+
+ public void setProjectPackageScan(ProjectPackageScan projectPackageScan) {
+ this.projectPackageScan = projectPackageScan;
+ }
+
+ public TypeToBeanNamesIndex getTypeToBeanNamesIndex() {
+ return typeToBeanNamesIndex;
+ }
+
+ public void setTypeToBeanNamesIndex(TypeToBeanNamesIndex typeToBeanNamesIndex) {
+ this.typeToBeanNamesIndex = typeToBeanNamesIndex;
+ }
+
+ public EntityClassToPropertiesIndex getEntityClassToPropertiesIndex() {
+ return entityClassToPropertiesIndex;
+ }
+
+ public void setEntityClassToPropertiesIndex(EntityClassToPropertiesIndex entityClassToPropertiesIndex) {
+ this.entityClassToPropertiesIndex = entityClassToPropertiesIndex;
+ }
+}
\ No newline at end of file
diff --git a/java-frontend/src/main/java/org/sonar/java/model/springcontext/TypeToBeanNamesIndex.java b/java-frontend/src/main/java/org/sonar/java/model/springcontext/TypeToBeanNamesIndex.java
new file mode 100644
index 00000000000..b21e6e561af
--- /dev/null
+++ b/java-frontend/src/main/java/org/sonar/java/model/springcontext/TypeToBeanNamesIndex.java
@@ -0,0 +1,56 @@
+/*
+ * SonarQube Java
+ * Copyright (C) SonarSource Sàrl
+ * mailto:info AT sonarsource DOT com
+ *
+ * You can redistribute and/or modify this program under the terms of
+ * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonar.java.model.springcontext;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Index mapping fully-qualified bean type names to the names of all beans of that type
+ * discovered during Spring context scanning.
+ *
+ * A single type may have multiple bean names registered (e.g. when the same class is
+ * declared as several distinct beans). Lookup returns an empty set for types with no
+ * registered beans.
+ */
+public class TypeToBeanNamesIndex {
+ /** Bean names indexed by fully-qualified type name. */
+ private final Map> beanNamesByType = new HashMap<>();
+
+ /**
+ * Registers a bean name under the given type.
+ *
+ * @param beanType fully-qualified class name of the bean's type
+ * @param beanName the bean name to associate with that type
+ */
+ public void addBeanForType(String beanType, String beanName) {
+ beanNamesByType.computeIfAbsent(beanType, k -> new HashSet<>()).add(beanName);
+ }
+
+ /**
+ * Returns an immutable set of all bean names registered for the given type.
+ *
+ * @param beanType fully-qualified class name of the bean's type
+ * @return an unmodifiable set of bean names, or an empty set if none were registered
+ */
+ public Set getNamesForType(String beanType) {
+ return Collections.unmodifiableSet(beanNamesByType.getOrDefault(beanType, Set.of()));
+ }
+}
\ No newline at end of file
diff --git a/java-frontend/src/main/java/org/sonar/java/model/springcontext/package-info.java b/java-frontend/src/main/java/org/sonar/java/model/springcontext/package-info.java
new file mode 100644
index 00000000000..d7f5beee53b
--- /dev/null
+++ b/java-frontend/src/main/java/org/sonar/java/model/springcontext/package-info.java
@@ -0,0 +1,35 @@
+/*
+ * SonarQube Java
+ * Copyright (C) SonarSource Sàrl
+ * mailto:info AT sonarsource DOT com
+ *
+ * You can redistribute and/or modify this program under the terms of
+ * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+
+/**
+ * Model of the Spring application context built during project scanning.
+ *
+ * Classes in this package are populated by visitors that traverse the AST and collect
+ * Spring-specific metadata. The resulting model is then consumed by rules that reason about
+ * the application context — for example, detecting duplicate bean definitions or missing
+ * component-scan coverage.
+ *
+ *
The central entry point is {@link org.sonar.java.model.springcontext.SpringContextModel},
+ * which aggregates the following indexes:
+ *
+ * - {@link org.sonar.java.model.springcontext.BeanDefinitionRegistry} — bean definitions by name
+ * - {@link org.sonar.java.model.springcontext.TypeToBeanNamesIndex} — bean names by type
+ * - {@link org.sonar.java.model.springcontext.ProjectPackageScan} — component-scan packages by module
+ * - {@link org.sonar.java.model.springcontext.EntityClassToPropertiesIndex} — JPA {@code @Entity} properties
+ *
+ */
+package org.sonar.java.model.springcontext;
\ No newline at end of file