diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 07edae63..2c9e3a72 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -31,4 +31,4 @@ jobs:
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
- run: ./mvnw -B clean deploy -Pci,artifactory
+ run: ./mvnw -B clean deploy -Pci,artifactory,nullaway
diff --git a/.mvn/jvm.config b/.mvn/jvm.config
new file mode 100644
index 00000000..32599cef
--- /dev/null
+++ b/.mvn/jvm.config
@@ -0,0 +1,10 @@
+--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
diff --git a/pom.xml b/pom.xml
index 20d6b09a..83bed74a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,10 +37,12 @@
1.4.0
3.6.2
+ 2.36.0
4.16.1
7.0.0.202409031743-r
1.5.2
2023.3.1
+ 0.12.7
UTF-8
UTF-8
4.0.0-SNAPSHOT
@@ -431,6 +433,54 @@ limitations under the License.
+
+ nullaway
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ true
+
+
+
+
+ default-compile
+ none
+
+
+ java-compile
+ compile
+
+ compile
+
+
+
+
+ com.google.errorprone
+ error_prone_core
+ ${errorprone.version}
+
+
+ com.uber.nullaway
+ nullaway
+ ${nullaway.version}
+
+
+
+ -XDcompilePolicy=simple
+ --should-stop=ifError=FLOW
+ -Xplugin:ErrorProne -XepDisableAllChecks -Xep:NullAway:ERROR -XepOpt:NullAway:OnlyNullMarked=true -XepOpt:NullAway:CustomContractAnnotations=org.springframework.lang.Contract
+
+
+
+
+
+
+
+
+
diff --git a/spring-modulith-actuator/pom.xml b/spring-modulith-actuator/pom.xml
index 671129ba..c590ead6 100644
--- a/spring-modulith-actuator/pom.xml
+++ b/spring-modulith-actuator/pom.xml
@@ -16,6 +16,11 @@
+
+ org.jspecify
+ jspecify
+
+
org.springframework.modulith
spring-modulith-runtime
diff --git a/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/autoconfigure/package-info.java b/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/autoconfigure/package-info.java
index d81ff6fc..113b0d46 100644
--- a/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/autoconfigure/package-info.java
+++ b/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/autoconfigure/package-info.java
@@ -1,5 +1,5 @@
/**
* Autoconfiguration for Spring Modulith actuators.
*/
-@org.springframework.lang.NonNullApi
+@org.jspecify.annotations.NullMarked
package org.springframework.modulith.actuator.autoconfigure;
diff --git a/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/package-info.java b/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/package-info.java
index 19489a32..b1ad5399 100644
--- a/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/package-info.java
+++ b/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/package-info.java
@@ -1,5 +1,5 @@
/**
* Spring Boot actuator support for Spring Modulith.
*/
-@org.springframework.lang.NonNullApi
+@org.jspecify.annotations.NullMarked
package org.springframework.modulith.actuator;
diff --git a/spring-modulith-api/pom.xml b/spring-modulith-api/pom.xml
index 3d12d714..6fbb6094 100644
--- a/spring-modulith-api/pom.xml
+++ b/spring-modulith-api/pom.xml
@@ -17,6 +17,11 @@
+
+ org.jspecify
+ jspecify
+
+
org.springframework.boot
spring-boot-autoconfigure
diff --git a/spring-modulith-api/src/main/java/org/springframework/modulith/package-info.java b/spring-modulith-api/src/main/java/org/springframework/modulith/package-info.java
index b308e837..b35ed2b0 100644
--- a/spring-modulith-api/src/main/java/org/springframework/modulith/package-info.java
+++ b/spring-modulith-api/src/main/java/org/springframework/modulith/package-info.java
@@ -1,5 +1,5 @@
/**
* Core abstractions of Spring Modulith. To be referred to in user applications.
*/
-@org.springframework.lang.NonNullApi
+@org.jspecify.annotations.NullMarked
package org.springframework.modulith;
diff --git a/spring-modulith-apt/pom.xml b/spring-modulith-apt/pom.xml
index cfae0220..3b6778f3 100644
--- a/spring-modulith-apt/pom.xml
+++ b/spring-modulith-apt/pom.xml
@@ -21,6 +21,10 @@
aptk-tools
0.29.0
+
+ org.jspecify
+ jspecify
+
org.springframework.boot
diff --git a/spring-modulith-apt/src/main/java/org/springframework/modulith/apt/SpringModulithProcessor.java b/spring-modulith-apt/src/main/java/org/springframework/modulith/apt/SpringModulithProcessor.java
index 04008243..4cf88144 100644
--- a/spring-modulith-apt/src/main/java/org/springframework/modulith/apt/SpringModulithProcessor.java
+++ b/spring-modulith-apt/src/main/java/org/springframework/modulith/apt/SpringModulithProcessor.java
@@ -19,6 +19,7 @@ import io.toolisticon.aptk.tools.ElementUtils;
import io.toolisticon.aptk.tools.wrapper.ElementWrapper;
import io.toolisticon.aptk.tools.wrapper.ExecutableElementWrapper;
import io.toolisticon.aptk.tools.wrapper.TypeElementWrapper;
+import org.jspecify.annotations.Nullable;
import java.io.File;
import java.io.FileWriter;
@@ -50,7 +51,6 @@ import javax.tools.Diagnostic.Kind;
import javax.tools.StandardLocation;
import org.springframework.boot.json.JsonWriter;
-import org.springframework.lang.Nullable;
import org.springframework.modulith.docs.metadata.MethodMetadata;
import org.springframework.modulith.docs.metadata.TypeMetadata;
import org.springframework.modulith.docs.util.BuildSystemUtils;
diff --git a/spring-modulith-core/pom.xml b/spring-modulith-core/pom.xml
index e936aabe..a58bf73f 100644
--- a/spring-modulith-core/pom.xml
+++ b/spring-modulith-core/pom.xml
@@ -17,6 +17,11 @@
+
+ org.jspecify
+ jspecify
+
+
org.springframework.modulith
spring-modulith-api
diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModule.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModule.java
index 621feaed..0aa1a280 100644
--- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModule.java
+++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModule.java
@@ -36,7 +36,7 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.Nullable;
import org.springframework.modulith.core.Types.JMoleculesTypes;
import org.springframework.modulith.core.Types.JavaTypes;
import org.springframework.modulith.core.Types.SpringTypes;
@@ -427,7 +427,7 @@ public class ApplicationModule implements Comparable {
var candidatePackageName = PackageName.ofType(candidate);
- return (candidatePackageName.isEmpty() || basePackage.getPackageName().contains(candidatePackageName))
+ return (PackageName.isDefault(candidatePackageName) || basePackage.getPackageName().contains(candidatePackageName))
&& getType(candidate).isPresent();
}
diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleDependencies.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleDependencies.java
index 03f0be13..2536b2c6 100644
--- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleDependencies.java
+++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleDependencies.java
@@ -21,6 +21,7 @@ import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
+import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@@ -172,7 +173,7 @@ public class ApplicationModuleDependencies {
* @param name must not be {@literal null} or empty.
* @return will never be {@literal null}.
*/
- public ApplicationModule getModuleByType(String name) {
+ public @Nullable ApplicationModule getModuleByType(String name) {
Assert.hasText(name, "Name must not be null or empty!");
diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleSource.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleSource.java
index 3a9a8cfb..29ed250b 100644
--- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleSource.java
+++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleSource.java
@@ -21,6 +21,7 @@ import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
+import org.jspecify.annotations.Nullable;
import org.springframework.modulith.ApplicationModule;
import org.springframework.modulith.core.Types.JMoleculesTypes;
import org.springframework.util.Assert;
@@ -240,7 +241,7 @@ public class ApplicationModuleSource {
* @param delegates must not be {@literal null}.
* @return will never be {@literal null}.
*/
- private static ApplicationModuleSourceMetadata delegating(ApplicationModuleSourceMetadata... delegates) {
+ private static ApplicationModuleSourceMetadata delegating(@Nullable ApplicationModuleSourceMetadata... delegates) {
return new ApplicationModuleSourceMetadata() {
diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleSourceFactory.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleSourceFactory.java
index 5f00f973..22276da1 100644
--- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleSourceFactory.java
+++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleSourceFactory.java
@@ -20,7 +20,7 @@ import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* SPI to allow build units contribute additional {@link ApplicationModuleSource}s in the form of either declaring them
diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java
index 390b0ccc..92699336 100644
--- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java
+++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java
@@ -32,9 +32,9 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
+import org.jspecify.annotations.Nullable;
import org.springframework.aot.generate.Generated;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
-import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.function.SingletonSupplier;
@@ -61,7 +61,7 @@ public class ApplicationModules implements Iterable {
private static final Map CACHE = new ConcurrentHashMap<>();
private static final ImportOption IMPORT_OPTION = new ImportOption.DoNotIncludeTests();
- private static final DescribedPredicate IS_GENERATED;
+ private static final @Nullable DescribedPredicate IS_GENERATED;
private static final DescribedPredicate IS_SPRING_CGLIB_PROXY = nameContaining("$$SpringCGLIB$$");
static {
@@ -624,9 +624,9 @@ public class ApplicationModules implements Iterable {
* {@literal null} or its type does not reside in any module.
*
* @param object can be {@literal null}.
- * @return
+ * @return can be {@literal null}.
*/
- private Integer getModuleIndexFor(@Nullable Object object) {
+ private @Nullable Integer getModuleIndexFor(@Nullable Object object) {
return Optional.ofNullable(object)
.map(it -> Class.class.isInstance(it) ? Class.class.cast(it) : it.getClass())
@@ -778,7 +778,7 @@ public class ApplicationModules implements Iterable {
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (obj == this) {
return true;
diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ArchitecturallyEvidentType.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ArchitecturallyEvidentType.java
index 5e6d1606..5f49f518 100644
--- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ArchitecturallyEvidentType.java
+++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ArchitecturallyEvidentType.java
@@ -20,6 +20,7 @@ import static org.springframework.modulith.core.Types.JavaXTypes.*;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
@@ -676,7 +677,9 @@ public abstract class ArchitecturallyEvidentType {
var attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(method.reflect(),
SpringTypes.AT_EVENT_LISTENER, false, false);
- return List.of(attributes.getClassArray("classes"));
+ return attributes == null
+ ? Collections.emptyList()
+ : List.of(attributes.getClassArray("classes"));
}
/*
diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/Classes.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/Classes.java
index 6d3bf3ef..f118f234 100644
--- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/Classes.java
+++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/Classes.java
@@ -29,7 +29,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/FormattableType.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/FormattableType.java
index 656d55d7..3516b01a 100644
--- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/FormattableType.java
+++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/FormattableType.java
@@ -22,7 +22,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ModulithMetadata.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ModulithMetadata.java
index 98c16998..2822e711 100644
--- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ModulithMetadata.java
+++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ModulithMetadata.java
@@ -75,7 +75,12 @@ public interface ModulithMetadata {
return SpringBootModulithMetadata.of(javaPackage);
}
- var className = candidates.iterator().next().getBeanClassName();
+ var definition = candidates.iterator().next();
+ var className = definition.getBeanClassName();
+
+ if (className == null) {
+ throw new IllegalStateException("No bean class name found on BeanDefinition %s!".formatted(definition));
+ }
return of(ClassUtils.resolveClassName(className, ModulithMetadata.class.getClassLoader()));
}
diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/NamedInterfaces.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/NamedInterfaces.java
index e5223534..bb21f161 100644
--- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/NamedInterfaces.java
+++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/NamedInterfaces.java
@@ -298,6 +298,10 @@ public class NamedInterfaces implements Iterable {
var annotation = AnnotatedElementUtils.getMergedAnnotation(it.reflect(),
org.springframework.modulith.NamedInterface.class);
+ if (annotation == null) {
+ throw new IllegalStateException("No @NamedInterface annotation found!");
+ }
+
NamedInterface.getDefaultedNames(annotation, it.getPackageName())
.forEach(name -> mappings.add(name, it));
});
diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/PackageName.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/PackageName.java
index c62ca790..7c2e95ed 100644
--- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/PackageName.java
+++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/PackageName.java
@@ -18,10 +18,11 @@ package org.springframework.modulith.core;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -34,6 +35,8 @@ import org.springframework.util.ClassUtils;
*/
public class PackageName implements Comparable {
+ public static final String DEFAULT = "<>";
+
private static final Map PACKAGE_NAMES = new HashMap<>();
private final String name;
@@ -77,6 +80,20 @@ public class PackageName implements Comparable {
return PackageName.of(ClassUtils.getPackageName(fullyQualifiedName));
}
+ /**
+ * Creates a new {@link PackageName} for the given fully-qualified type name.
+ *
+ * @param fullyQualifiedName must not be {@literal null} or empty.
+ * @return will never be {@literal null}.
+ * @since 2.0
+ */
+ public static PackageName ofType(Class> type) {
+
+ Assert.notNull(type, "Type must not be null!");
+
+ return PackageName.of(ClassUtils.getPackageName(type));
+ }
+
/**
* Returns the {@link PackageName} with the given name.
*
@@ -88,7 +105,9 @@ public class PackageName implements Comparable {
Assert.notNull(name, "Name must not be null!");
- return PACKAGE_NAMES.computeIfAbsent(name, PackageName::new);
+ var defaulted = name.isBlank() ? DEFAULT : name;
+
+ return PACKAGE_NAMES.computeIfAbsent(defaulted, PackageName::new);
}
/**
@@ -107,6 +126,16 @@ public class PackageName implements Comparable {
return PACKAGE_NAMES.computeIfAbsent(name, it -> new PackageName(name, segments));
}
+ /**
+ * Returns whether the given {@link PackageName} is the default package name (logically an empty string).
+ *
+ * @param name must not be {@literal null}.
+ * @since 2.0
+ */
+ static boolean isDefault(PackageName name) {
+ return name.hasName(DEFAULT);
+ }
+
/**
* Returns the length of the package name.
*
@@ -281,7 +310,9 @@ public class PackageName implements Comparable {
return Stream.of(reference);
}
- return Stream.concat(expandUntil(reference.getParent()), Stream.of(reference));
+ var parent = Objects.requireNonNull(reference.getParent());
+
+ return Stream.concat(expandUntil(parent), Stream.of(reference));
}
/**
@@ -318,7 +349,7 @@ public class PackageName implements Comparable {
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (obj == this) {
return true;
diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/SpringBootModulithMetadata.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/SpringBootModulithMetadata.java
index 57a54e3e..85458a29 100644
--- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/SpringBootModulithMetadata.java
+++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/SpringBootModulithMetadata.java
@@ -21,8 +21,8 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.annotation.AnnotatedElementUtils;
-import org.springframework.lang.NonNull;
import org.springframework.modulith.core.Types.SpringTypes;
import org.springframework.util.Assert;
@@ -34,20 +34,21 @@ import org.springframework.util.Assert;
*/
class SpringBootModulithMetadata implements ModulithMetadata {
- private static final Class extends Annotation> AT_SPRING_BOOT_APPLICATION = Types
+ private static final @Nullable Class extends Annotation> AT_SPRING_BOOT_APPLICATION = Types
.loadIfPresent(SpringTypes.AT_SPRING_BOOT_APPLICATION);
- private final @NonNull Object source;
- private final String systemName, basePackage;
+ private final Object source;
+ private final String basePackage;
+ private final @Nullable String systemName;
/**
* Creates a new {@link SpringBootModulithMetadata} for the given source.
*
* @param source must not be {@literal null}.
- * @param systemName can be {@literal null}.
* @param basePackage must not be {@literal null}.
+ * @param systemName can be {@literal null}.
*/
- private SpringBootModulithMetadata(Object source, String systemName, String basePackage) {
+ private SpringBootModulithMetadata(Object source, String basePackage, @Nullable String systemName) {
Assert.notNull(source, "Source must not be null!");
Assert.notNull(basePackage, "Base package must not be null!");
@@ -71,7 +72,7 @@ class SpringBootModulithMetadata implements ModulithMetadata {
return Optional.ofNullable(AT_SPRING_BOOT_APPLICATION) //
.filter(it -> AnnotatedElementUtils.hasAnnotation(annotated, it)) //
- .map(__ -> new SpringBootModulithMetadata(annotated, annotated.getSimpleName(), annotated.getPackageName()));
+ .map(__ -> new SpringBootModulithMetadata(annotated, annotated.getPackageName(), annotated.getSimpleName()));
}
/**
@@ -84,7 +85,7 @@ class SpringBootModulithMetadata implements ModulithMetadata {
Assert.hasText(javaPackage, "Package name must not be null or empty!");
- return new SpringBootModulithMetadata(javaPackage, null, javaPackage);
+ return new SpringBootModulithMetadata(javaPackage, javaPackage, null);
}
/*
diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/Types.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/Types.java
index 70b4c372..048d74b3 100644
--- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/Types.java
+++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/Types.java
@@ -26,7 +26,7 @@ import java.util.function.Predicate;
import org.jmolecules.archunit.JMoleculesArchitectureRules;
import org.jmolecules.archunit.JMoleculesDddRules;
import org.jmolecules.ddd.annotation.Module;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.Nullable;
import org.springframework.modulith.PackageInfo;
import org.springframework.modulith.core.ApplicationModuleSource.ApplicationModuleSourceMetadata;
import org.springframework.util.Assert;
@@ -82,7 +82,7 @@ public class Types {
static final String AT_DOMAIN_EVENT = BASE_PACKAGE + ".event.annotation.DomainEvent";
static final String DOMAIN_EVENT = BASE_PACKAGE + ".event.types.DomainEvent";
- private static Collection RULES;
+ private static @Nullable Collection RULES;
/**
* Returns whether jMolecules is generally present.
@@ -123,34 +123,38 @@ public class Types {
*/
public static Collection getRules() {
- if (RULES == null) {
+ var rules = RULES;
+
+ if (rules == null) {
var classLoader = JMoleculesTypes.class.getClassLoader();
- RULES = new ArrayList();
+ rules = new ArrayList();
if (ClassUtils.isPresent(DDD_RULES, classLoader)) {
- RULES.add(JMoleculesDddRules.all());
+ rules.add(JMoleculesDddRules.all());
}
if (!ClassUtils.isPresent(ARCHITECTURE_RULES, classLoader)) {
- return RULES;
+ return rules;
}
if (ClassUtils.isPresent(HEXAGONAL, classLoader)) {
- RULES.add(JMoleculesArchitectureRules.ensureHexagonal());
+ rules.add(JMoleculesArchitectureRules.ensureHexagonal());
}
if (ClassUtils.isPresent(LAYERED, classLoader)) {
- RULES.add(JMoleculesArchitectureRules.ensureLayering());
+ rules.add(JMoleculesArchitectureRules.ensureLayering());
}
if (ClassUtils.isPresent(ONION, classLoader)) {
- RULES.add(JMoleculesArchitectureRules.ensureOnionClassical());
- RULES.add(JMoleculesArchitectureRules.ensureOnionSimple());
+ rules.add(JMoleculesArchitectureRules.ensureOnionClassical());
+ rules.add(JMoleculesArchitectureRules.ensureOnionSimple());
}
+
+ RULES = rules;
}
- return RULES;
+ return rules;
}
}
diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/config/package-info.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/config/package-info.java
index 488ce2d5..5ce3aeeb 100644
--- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/config/package-info.java
+++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/config/package-info.java
@@ -1,5 +1,5 @@
/**
* Core configuration abstractions of Spring Modulith.
*/
-@org.springframework.lang.NonNullApi
+@org.jspecify.annotations.NullMarked
package org.springframework.modulith.core.config;
diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/package-info.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/package-info.java
index b9eca324..2321e3ea 100644
--- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/package-info.java
+++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/package-info.java
@@ -1,5 +1,5 @@
/**
* Core, internal abstractions of Spring Modulith.
*/
-@org.springframework.lang.NonNullApi
+@org.jspecify.annotations.NullMarked
package org.springframework.modulith.core;
diff --git a/spring-modulith-core/src/test/java/org/springframework/modulith/core/ApplicationModulesUnitTests.java b/spring-modulith-core/src/test/java/org/springframework/modulith/core/ApplicationModulesUnitTests.java
index c1cb9438..9ed32289 100644
--- a/spring-modulith-core/src/test/java/org/springframework/modulith/core/ApplicationModulesUnitTests.java
+++ b/spring-modulith-core/src/test/java/org/springframework/modulith/core/ApplicationModulesUnitTests.java
@@ -80,4 +80,12 @@ class ApplicationModulesUnitTests {
it -> assertThat(it).contains("Invalid", "'invalid'", "'ni.nested.b.first'"),
it -> assertThat(it).contains("Invalid", "'ni'", "'ni.nested.b.first'"));
}
+
+ @Test // GH-1192
+ void findsTypeBySimpleName() {
+
+ assertThat(modules.getModuleByName("ni")).hasValueSatisfying(it -> {
+ assertThat(it.contains("RootType")).isTrue();
+ });
+ }
}
diff --git a/spring-modulith-docs/pom.xml b/spring-modulith-docs/pom.xml
index 42dcdcb8..51d3d4e3 100644
--- a/spring-modulith-docs/pom.xml
+++ b/spring-modulith-docs/pom.xml
@@ -17,6 +17,11 @@
+
+ org.jspecify
+ jspecify
+
+
${project.groupId}
spring-modulith-core
diff --git a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java
index 33fa69c1..b5d0f69c 100644
--- a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java
+++ b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java
@@ -25,9 +25,9 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
+import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.lang.Nullable;
import org.springframework.modulith.core.ApplicationModule;
import org.springframework.modulith.core.ApplicationModuleDependency;
import org.springframework.modulith.core.ApplicationModules;
diff --git a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/ConfigurationProperties.java b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/ConfigurationProperties.java
index 4eae7425..1f0666e6 100644
--- a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/ConfigurationProperties.java
+++ b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/ConfigurationProperties.java
@@ -23,9 +23,9 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
-import org.springframework.lang.Nullable;
import org.springframework.modulith.core.ApplicationModule;
import org.springframework.modulith.docs.ConfigurationProperties.ConfigurationProperty;
import org.springframework.util.Assert;
@@ -124,7 +124,6 @@ class ConfigurationProperties implements Iterable {
static record ConfigurationProperty(String name, @Nullable String description, String type, String sourceType,
@Nullable String defaultValue) {
- @SuppressWarnings("null")
static Stream of(Map source) {
String sourceType = getAsString(source, "sourceType");
@@ -133,9 +132,9 @@ class ConfigurationProperties implements Iterable {
return Stream.empty();
}
- ConfigurationProperty property = new ConfigurationProperty(getAsString(source, "name"),
+ ConfigurationProperty property = new ConfigurationProperty(getRequiredAsString(source, "name"),
getAsString(source, "description"),
- getAsString(source, "type"),
+ getRequiredAsString(source, "type"),
sourceType,
getAsString(source, "defaultValue"));
@@ -146,6 +145,15 @@ class ConfigurationProperties implements Iterable {
return StringUtils.hasText(sourceType);
}
+ private static String getRequiredAsString(Map source, String key) {
+
+ var value = getAsString(source, key);
+
+ Assert.notNull(value, "No value found for key %s in %s!".formatted(key, source));
+
+ return value;
+ }
+
private static @Nullable String getAsString(Map source, String key) {
Object value = source.get(key);
diff --git a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Documenter.java b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Documenter.java
index f2890a7f..0bf912aa 100644
--- a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Documenter.java
+++ b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Documenter.java
@@ -36,7 +36,8 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.Nullable;
+import org.springframework.lang.Contract;
import org.springframework.modulith.core.ApplicationModule;
import org.springframework.modulith.core.ApplicationModules;
import org.springframework.modulith.core.DependencyDepth;
@@ -98,7 +99,7 @@ public class Documenter {
private boolean cleared;
- private Map components;
+ private @Nullable Map components;
/**
* Creates a new {@link Documenter} for the {@link ApplicationModules} created for the given modulith type in the
@@ -223,6 +224,7 @@ public class Documenter {
* @return the current instance, will never be {@literal null}.
* @since 1.2.2
*/
+ @Contract("_, _ -> this")
public Documenter writeAggregatingDocument(DiagramOptions diagramOptions, CanvasOptions canvasOptions) {
Assert.notNull(diagramOptions, "DiagramOptions must not be null!");
@@ -485,18 +487,19 @@ public class Documenter {
.forEach(it -> it.addTags(DependencyType.USES_COMPONENT.toString()));
}
+ @SuppressWarnings("null")
private Map getComponents(DiagramOptions options) {
- if (components == null) {
+ if (this.components == null) {
this.components = modules.stream() //
.collect(Collectors.toMap(Function.identity(),
it -> container.addComponent(options.defaultDisplayName.apply(it), "", "Module")));
- this.components.forEach((key, value) -> addDependencies(key, value, options));
+ components.forEach((key, value) -> addDependencies(key, value, options));
}
- return components;
+ return this.components;
}
private void addComponentsToView(ApplicationModule module, ComponentView view, DiagramOptions options) {
@@ -643,6 +646,11 @@ public class Documenter {
Styles styles) {
var component = components.get(module);
+
+ if (component == null) {
+ throw new IllegalStateException("Couldn't find component for module %s!".formatted(module));
+ }
+
var selector = options.colorSelector;
// Apply custom color if configured
@@ -1061,6 +1069,7 @@ public class Documenter {
return new CanvasOptions(groupers, apiBase, targetFileName, hideInternals, hideEmptyLines);
}
+ @Nullable
String getApiBase() {
return apiBase;
}
@@ -1083,7 +1092,10 @@ public class Documenter {
// Wipe entries without any beans
new HashSet<>(result.keySet()).forEach(key -> {
- if (result.get(key).isEmpty()) {
+
+ var value = result.get(key);
+
+ if (value != null && value.isEmpty()) {
result.remove(key);
}
});
@@ -1161,10 +1173,10 @@ public class Documenter {
*
* @param name must not be {@literal null} or empty.
* @param predicate must not be {@literal null}.
- * @param description must not be {@literal null} or empty.
+ * @param description can be {@literal null}.
* @return will never be {@literal null}.
*/
- public static Grouping of(String name, Predicate predicate, String description) {
+ public static Grouping of(String name, Predicate predicate, @Nullable String description) {
return new Grouping(name, predicate, description);
}
diff --git a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/SpringModulithDocumentationSource.java b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/SpringModulithDocumentationSource.java
index c6c77971..c6aa46f5 100644
--- a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/SpringModulithDocumentationSource.java
+++ b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/SpringModulithDocumentationSource.java
@@ -23,9 +23,9 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
+import org.jspecify.annotations.Nullable;
import org.springframework.boot.json.BasicJsonParser;
import org.springframework.core.io.Resource;
-import org.springframework.lang.Nullable;
import org.springframework.modulith.docs.metadata.MethodMetadata;
import org.springframework.modulith.docs.metadata.TypeMetadata;
import org.springframework.modulith.docs.util.BuildSystemUtils;
@@ -134,28 +134,39 @@ class SpringModulithDocumentationSource implements DocumentationSource {
@SuppressWarnings("unchecked")
private static TypeMetadata typeMetadata(Map source) {
- var methods = source.containsKey("methods")
- ? ((List