diff --git a/spring-modulith-apt/src/main/java/io/toolisticon/aptk/tools/wrapper/TypeElementWrapper.java b/spring-modulith-apt/src/main/java/io/toolisticon/aptk/tools/wrapper/TypeElementWrapper.java
new file mode 100644
index 00000000..f8967bb5
--- /dev/null
+++ b/spring-modulith-apt/src/main/java/io/toolisticon/aptk/tools/wrapper/TypeElementWrapper.java
@@ -0,0 +1,395 @@
+package io.toolisticon.aptk.tools.wrapper;
+
+import io.toolisticon.aptk.tools.ElementUtils;
+import io.toolisticon.aptk.tools.TypeMirrorWrapper;
+import io.toolisticon.aptk.tools.corematcher.AptkCoreMatchers;
+import io.toolisticon.aptk.tools.fluentfilter.FluentElementFilter;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.NestingKind;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * Copy of the original {@link TypeElementWrapper} of the Toolisticon APTK project located
+ * here. Tweaks {@link #getOuterType()} to include
+ * {@link ElementKind#INTERFACE} in the outer class lookup to fix the issue reported
+ * here.
+ * Wraps a TypeElement to provide some convenience * functionality
+ */
+public class TypeElementWrapper extends ElementWrapper {
+
+ private final static String TYPE_ELEMENT_CLASS_NAME = "javax.lang.model.element.TypeElement";
+
+ /**
+ * Hidden constructor.
+ *
+ * @param typeElement The TypeElement to wrap
+ */
+ private TypeElementWrapper(TypeElement typeElement) {
+ super(typeElement);
+ }
+
+ // methods inherited from TypeElement
+
+ /**
+ * Returns the NestingKind.
+ *
+ * @return the NestingKind.
+ */
+ public NestingKind getNestingKind() {
+ return this.element.getNestingKind();
+ }
+
+ public boolean hasNestingKind(NestingKind nestingKind) {
+ return nestingKind != null && getNestingKind().equals(nestingKind);
+ }
+
+ public boolean isNested() {
+ return getNestingKind().isNested();
+ }
+
+ /**
+ * Gets the nested name of the element. If the TypeElement isn't a nested type the simple name will be returned.
+ *
+ * @return the nested name of the element
+ */
+ public String getNestedName() {
+
+ String name = getSimpleName();
+
+ TypeElementWrapper current = this;
+
+ while (current.isNested()) {
+
+ current = current.getOuterType().get();
+ name = current.getSimpleName() + "." + name;
+
+ }
+
+ return name;
+ }
+
+ /**
+ * Returns the qualified name.
+ *
+ * @return the qualified name.
+ */
+ public String getQualifiedName() {
+ return this.element.getQualifiedName().toString();
+ }
+
+ /**
+ * Check for qualified name.
+ *
+ * @param name the name to check for
+ * @return true if qualified name matches passed name, otherwise false
+ */
+ public boolean hasQualifiedName(String name) {
+ return name != null && getQualifiedName().equals(name);
+ }
+
+ /**
+ * Gets the binary name for the type element.
+ */
+ public String getBinaryName() {
+
+ // According to https://docs.oracle.com/javase/specs/jls/se8/html/jls-13.html the binary name looks like:
+ // -- The binary name of a top level type (§7.6) is its canonical name (§6.7).
+ // -- The binary name of a member type (§8.5, §9.5) consists of the binary name of its immediately enclosing type,
+ // followed by $, followed by the simple name of the member.
+
+ if (this.isNested()) {
+ // Safe to call since its of nested kind
+ TypeElementWrapper enclosingTypeElement = this.getOuterType().get();
+ return enclosingTypeElement.getBinaryName() + "$" + this.getSimpleName();
+ } else {
+ return this.getQualifiedName();
+ }
+
+ }
+
+ /**
+ * Gets the direct superclass
+ *
+ * @return a TypeMirrorWrapper of the direct superclass
+ */
+ public TypeMirrorWrapper getSuperclass() {
+ return TypeMirrorWrapper.wrap(this.element.getSuperclass());
+ }
+
+ /**
+ * Returns list of TypeMirrorWrapper containing all directly implemented interfaces. Doesn't return interfaces
+ * implemented by superclasses.
+ *
+ * @return list of TypeMirrorWrapper containing all directly implemented interfaces
+ */
+ public List getInterfaces() {
+ return this.element.getInterfaces().stream().map(TypeMirrorWrapper::wrap).collect(Collectors.toList());
+ }
+
+ /**
+ * Returns Set of TypeMirrorWrapper containing all implemented interfaces (including those of superclasses).
+ *
+ * @return Set of TypeMirrorWrapper containing all implemented interfaces
+ */
+ public Set getAllInterfaces() {
+
+ // Add own interfaces
+ Set result = new HashSet<>(this.getInterfaces());
+
+ // recursively add those of superclasses.
+ Optional superClass = getSuperclass().getTypeElement();
+ superClass.ifPresent(typeElementWrapper -> result.addAll(typeElementWrapper.getAllInterfaces()));
+
+ return result;
+ }
+
+ /**
+ * Returns list of TypeParameterElementWrapper instances describing all TypeParameters.
+ *
+ * @return list of TypeParameterElementWrapper instances describing all TypeParameters, or an empty list
+ */
+ public List getTypeParameters() {
+ return this.element.getTypeParameters().stream().map(TypeParameterElementWrapper::wrap)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Checks if wrapped TypeElement has type parameters.
+ *
+ * @return true, if TypeElement has type parameters, otherwise false.
+ */
+ public boolean hasTypeParameters() {
+ return getTypeParameters().size() > 0;
+ }
+
+ /**
+ * Returns an enclosed method. Works only if all parameter types are already compiled.
+ *
+ * @param name the method name
+ * @param parameterTypes The parameter types
+ * @return the wrapped ExecutableElement instance if method is found, otherwise null
+ */
+ public Optional getMethod(String name, Class>... parameterTypes) {
+ List filterResult = FluentElementFilter
+ .createFluentElementFilter(this.element.getEnclosedElements())
+ .applyFilter(AptkCoreMatchers.IS_METHOD)
+ .applyFilter(AptkCoreMatchers.BY_NAME).filterByOneOf(name)
+ .applyFilter(AptkCoreMatchers.BY_PARAMETER_TYPE).filterByOneOf(parameterTypes)
+ .getResult();
+ return Optional.ofNullable(filterResult.size() > 0 ? ExecutableElementWrapper.wrap(filterResult.get(0)) : null);
+ }
+
+ /**
+ * Returns list of ExecutableElementWrapper for all methods in wrapped TypeElement, filtered by modifiers.
+ *
+ * @param modifier modifiers used for filtering
+ * @return list of ExecutableElementWrapper for all methods in wrapped TypeElement, or empty list
+ */
+ public List getMethods(Modifier... modifier) {
+ return FluentElementFilter.createFluentElementFilter(
+ this.element.getEnclosedElements()).applyFilter(AptkCoreMatchers.IS_METHOD)
+ .applyFilter(AptkCoreMatchers.BY_MODIFIER).filterByAllOf(modifier)
+ .getResult()
+ .stream().map(ExecutableElementWrapper::wrap).collect(Collectors.toList());
+ }
+
+ /**
+ * Returns list of VariableElementWrapper for all fields in wrapped TypeElement, filtered by modifiers.
+ *
+ * @param modifier modifiers used for filtering
+ * @return list of VariableElementWrapper for all fields in wrapped TypeElement, or empty list
+ */
+ public List getFields(Modifier... modifier) {
+ return FluentElementFilter.createFluentElementFilter(
+ this.element.getEnclosedElements()).applyFilter(AptkCoreMatchers.IS_FIELD)
+ .applyFilter(AptkCoreMatchers.BY_MODIFIER).filterByAllOf(modifier)
+ .getResult()
+ .stream().map(VariableElementWrapper::wrap).collect(Collectors.toList());
+ }
+
+ /**
+ * Returns Optional of VariableElementWrapper of field with passed name in wrapped TypeElement.
+ *
+ * @param name Name of the field
+ * @return Optional of ExecutableElementWrapper of field with passed name in wrapped TypeElement, or empty list
+ */
+ public Optional getFieldByName(String name) {
+ List fields = FluentElementFilter.createFluentElementFilter(
+ this.element.getEnclosedElements()).applyFilter(AptkCoreMatchers.IS_FIELD)
+ .applyFilter(AptkCoreMatchers.BY_NAME).filterByOneOf(name)
+ .getResult()
+ .stream().map(VariableElementWrapper::wrap).collect(Collectors.toList());
+
+ return Optional.ofNullable(fields.size() > 0 ? fields.get(0) : null);
+ }
+
+ /**
+ * Returns list of VariableElementWrapper for all constructors in wrapped TypeElement, filtered by modifiers.
+ *
+ * @param modifier modifiers used for filtering
+ * @return list of ExecutableElementWrapper for all constructors in wrapped TypeElement, or empty list
+ */
+ public List getConstructors(Modifier... modifier) {
+ return FluentElementFilter.createFluentElementFilter(
+ this.element.getEnclosedElements()).applyFilter(AptkCoreMatchers.IS_CONSTRUCTOR)
+ .applyFilter(AptkCoreMatchers.BY_MODIFIER).filterByAllOf(modifier)
+ .getResult()
+ .stream().map(ExecutableElementWrapper::wrap).collect(Collectors.toList());
+ }
+
+ /**
+ * Returns list of VariableElementWrapper for all inner types in wrapped TypeElement, filtered by modifiers.
+ *
+ * @param modifier modifiers used for filtering
+ * @return list of ExecutableElementWrapper for all inner types in wrapped TypeElement, or empty list
+ */
+ public List getInnerTypes(Modifier... modifier) {
+ return FluentElementFilter.createFluentElementFilter(
+ this.element.getEnclosedElements()).applyFilter(AptkCoreMatchers.IS_CLASS)
+ .applyFilter(AptkCoreMatchers.BY_MODIFIER).filterByAllOf(modifier)
+ .getResult()
+ .stream().map(TypeElementWrapper::wrap).collect(Collectors.toList());
+ }
+
+ /**
+ * Returns the direct outer type for nested classes. Returned type may not be a top level type, since java allows
+ * nested classes in nested classes.
+ *
+ * @return an Optional containing the outer type of nested classes, if present.
+ */
+ public Optional getOuterType() {
+ if (this.element.getNestingKind() != NestingKind.MEMBER) {
+ return Optional.empty();
+ }
+
+ return Optional.of(TypeElementWrapper.wrap(ElementUtils.AccessEnclosingElements
+ . getFirstEnclosingElementOfKind(this.element, ElementKind.CLASS, ElementKind.INTERFACE)));
+ }
+
+ /**
+ * Returns the direct outer type for nested classes.
+ *
+ * @return an Optional containing the outer type of nested classes, if present.
+ */
+ public Optional getOuterTopLevelType() {
+ if (this.element.getNestingKind() != NestingKind.MEMBER) {
+ return Optional.empty();
+ }
+
+ return Optional.of(this.getAllEnclosingElements().stream()
+ .filter(ElementWrapper::isTypeElement)
+ .map(ElementWrapper::toTypeElement)
+ .filter(e -> e.hasNestingKind(NestingKind.TOP_LEVEL)).collect(Collectors.toList()).get(0));
+
+ }
+
+ /**
+ * Gets all enum constant names of enum as VariableElements.
+ *
+ * @return A list containing all enum constant names or null if wrapped TypeElement is no enum.
+ */
+ public List getEnumValues() {
+ return isEnum() ? this.filterEnclosedElements()
+ .applyFilter(AptkCoreMatchers.BY_ELEMENT_KIND)
+ .filterByOneOf(ElementKind.ENUM_CONSTANT)
+ .getResult()
+ .stream().map(e -> VariableElementWrapper.wrap((VariableElement) e))
+ .collect(Collectors.toList())
+ : null;
+ }
+
+ /**
+ * Returns the record components of this class or interface element in declaration order.
+ *
+ * @return the record components, or an empty list if there are none
+ */
+ public List getRecordComponents() {
+
+ // This method is available from Java 16 - the introduction of records, so this check is sufficient to prevent
+ // reflective calling of method
+ if (!isRecord()) {
+ return Collections.EMPTY_LIST;
+ }
+ List extends Element> recordComponentElements = this
+ .> invokeParameterlessMethodOfElement(TYPE_ELEMENT_CLASS_NAME, "getRecordComponents");
+
+ return recordComponentElements.stream().map(RecordComponentElementWrapper::wrap).collect(Collectors.toList());
+
+ }
+
+ /**
+ * Returns the permitted classes of this class or interface element in declaration order.
+ *
+ * @return the permitted classes, or an empty list if there are none
+ */
+ public List getPermittedSubclasses() {
+
+ // must make sure that method exists, otherwise return the default value
+ if (!hasMethod(TYPE_ELEMENT_CLASS_NAME, "getPermittedSubclasses")) {
+ // TODO MUST CHECK WHAT SHOULD BE RETURNED FOR NON SEALED CLASSES!
+ return Collections.EMPTY_LIST;
+ }
+
+ List typeMirrors = this.> invokeParameterlessMethodOfElement(TYPE_ELEMENT_CLASS_NAME,
+ "getPermittedSubclasses");
+ return typeMirrors.stream().map(TypeMirrorWrapper::wrap).collect(Collectors.toList());
+ }
+
+ /**
+ * Wraps a TypeElement.
+ *
+ * @param element the TypeElement to wrap
+ * @return a wrapper instance
+ */
+ public static TypeElementWrapper wrap(TypeElement element) {
+ return new TypeElementWrapper(element);
+ }
+
+ /**
+ * Retrieve TypeElementWrapper by fully qualified name.
+ *
+ * @param fqn the fully qualified name of the element
+ * @return an Optional that contains the wrapper instance, if TypeElement for fqn exists
+ */
+ public static Optional getByFqn(String fqn) {
+ return getByTypeMirror(TypeMirrorWrapper.wrap(fqn));
+ }
+
+ /**
+ * Retrieve TypeElementWrapper by fully qualified name.
+ *
+ * @param clazz the class of the element
+ * @return an Optional that contains the wrapper instance, if TypeElement for class exists (only for DeclaredTypes)
+ */
+ public static Optional getByClass(Class> clazz) {
+ return getByTypeMirror(TypeMirrorWrapper.wrap(clazz));
+ }
+
+ /**
+ * Retrieve TypeElementWrapper by fully qualified name.
+ *
+ * @param typeMirror the TypeMirror of the element
+ * @return an Optional that contains the wrapper instance, if TypeElement for TypeMirror exists (only for
+ * DeclaredTypes)
+ */
+ public static Optional getByTypeMirror(TypeMirror typeMirror) {
+ return getByTypeMirror(TypeMirrorWrapper.wrap(typeMirror));
+ }
+
+ public static Optional getByTypeMirror(TypeMirrorWrapper typeMirror) {
+ return typeMirror.getTypeElement();
+ }
+}
diff --git a/spring-modulith-apt/src/test/java/org/springframework/modulith/apt/SpringModulithProcessorUnitTests.java b/spring-modulith-apt/src/test/java/org/springframework/modulith/apt/SpringModulithProcessorUnitTests.java
index 90094423..da8b17b6 100644
--- a/spring-modulith-apt/src/test/java/org/springframework/modulith/apt/SpringModulithProcessorUnitTests.java
+++ b/spring-modulith-apt/src/test/java/org/springframework/modulith/apt/SpringModulithProcessorUnitTests.java
@@ -63,6 +63,14 @@ class SpringModulithProcessorUnitTests {
assertThat(comment).isNotBlank().doesNotContain("\n");
}
+ @Test // GH-962
+ void extractsJavadocFromMethodWithClassNestedInInterfaceAsParameter() {
+
+ assertThatNoException().isThrownBy(() -> {
+ assertSucceded(getSourceFile("SampleInterface"));
+ });
+ }
+
private static DoCustomAssertions assertSucceded(String source) {
return assertSourceProcessed(source)
diff --git a/spring-modulith-apt/src/test/resources/example/SampleInterface.java b/spring-modulith-apt/src/test/resources/example/SampleInterface.java
new file mode 100644
index 00000000..38da4dfe
--- /dev/null
+++ b/spring-modulith-apt/src/test/resources/example/SampleInterface.java
@@ -0,0 +1,23 @@
+package example;
+
+/**
+ * Interface comment.
+ *
+ * @author Oliver Drotbohm
+ */
+interface SampleInterface {
+
+ /**
+ * Method with nested class parameter.
+ *
+ * @param parameter
+ */
+ void someMethod(SampleClass parameter);
+
+ /**
+ * Nested class comment.
+ *
+ * @author Oliver Drotbohm
+ */
+ class SampleClass {}
+}