diff --git a/operator-annotations/pom.xml b/operator-annotations/pom.xml
new file mode 100644
index 0000000000..2dd41a32c9
--- /dev/null
+++ b/operator-annotations/pom.xml
@@ -0,0 +1,18 @@
+
+
+ 4.0.0
+
+ io.javaoperatorsdk
+ java-operator-sdk
+ 5.1.3-SNAPSHOT
+
+
+ operator-annotations
+
+
+ UTF-8
+
+
+
\ No newline at end of file
diff --git a/operator-annotations/src/main/java/io/javaoperatorsdk/annotation/Sample.java b/operator-annotations/src/main/java/io/javaoperatorsdk/annotation/Sample.java
new file mode 100644
index 0000000000..62a39b0e3b
--- /dev/null
+++ b/operator-annotations/src/main/java/io/javaoperatorsdk/annotation/Sample.java
@@ -0,0 +1,26 @@
+package io.javaoperatorsdk.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation marks an integration test class as a sample for the documentation.
+ * Intended for use on test classes only.
+ *
+ *
Example:
+ *
{@code
+ * @Sample(
+ * tldr="Usage of PrimaryToSecondaryMapper",
+ * description="Showcases the usage of PrimaryToSecondaryMapper, in what situation it needs to be used and how to optimize typical uses with Informer indexes."
+ * )
+ * class PrimaryToSecondaryIT {
+ * // details omitted
+ * }
+ * }
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+@Documented
+public @interface Sample {
+ String tldr();
+ String description();
+}
\ No newline at end of file
diff --git a/operator-annotations/src/main/java/io/javaoperatorsdk/processor/SampleProcessor.java b/operator-annotations/src/main/java/io/javaoperatorsdk/processor/SampleProcessor.java
new file mode 100644
index 0000000000..6c7934189d
--- /dev/null
+++ b/operator-annotations/src/main/java/io/javaoperatorsdk/processor/SampleProcessor.java
@@ -0,0 +1,96 @@
+package io.javaoperatorsdk.processor;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.element.*;
+import javax.lang.model.util.Types;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.*;
+
+/**
+ * Annotation processor that generates a markdown file listing all classes annotated with @Sample.
+ */
+@SupportedAnnotationTypes("io.javaoperatorsdk.annotation.Sample")
+public class SampleProcessor extends AbstractProcessor {
+
+ private record SampleInfo(String tldr, String description) {}
+ private final List samples = new ArrayList<>();
+
+ @Override
+ public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
+
+ Types types = processingEnv.getTypeUtils();
+ for (TypeElement annotation: annotations) {
+ // element has details about the class being annotated, but not the values
+ // ex: String tldr = ..., it knows it has a field called tldr but not what's assigned
+ for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
+ // a mirror gives access to the values assigned to the fields of the annotation
+ // element.getAnnotation does not work since the retention is SOURCE
+ AnnotationMirror annotationMirror = element.getAnnotationMirrors().stream()
+ .filter(am -> types.isSameType(am.getAnnotationType(), annotation.asType()))
+ .findFirst()
+ .orElse(null);
+
+ if (annotationMirror != null) {
+ String tldr = getString(annotationMirror.getElementValues(), "tldr");
+ String description = getString(annotationMirror.getElementValues(), "description");
+
+ samples.add(new SampleInfo(tldr, description) );
+ }
+ }
+ }
+
+ if (roundEnv.processingOver()) {
+ // sort to keep the order stable
+ samples.sort(Comparator.comparing(SampleInfo::tldr, String.CASE_INSENSITIVE_ORDER));
+ writeSampleMDFile(samples);
+ }
+ return false;
+ }
+
+ /**
+ *
+ */
+ private void writeSampleMDFile(List samples) {
+ try {
+ FileObject fileObject = processingEnv.getFiler()
+ .createResource(StandardLocation.CLASS_OUTPUT, "", "samples.md");
+
+ try(Writer writer = fileObject.openWriter();) {
+ writer.write("# Integration Test Samples \n");
+
+ for (SampleInfo sample : samples) {
+ writer.write("## " + sample.tldr() + "\n");
+ writer.write(sample.description() + "\n\n");
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Extracts a string value from the annotation values map.
+ * @param vals the map of annotation values
+ * @param name the name of the field to extract
+ * @return the string value, or empty string if not found
+ */
+ private String getString(
+ Map extends ExecutableElement, ? extends AnnotationValue> vals, String name) {
+ for (Map.Entry extends ExecutableElement, ? extends AnnotationValue> ev : vals.entrySet()) {
+ if (ev.getKey().getSimpleName().contentEquals(name)) {
+ Object value = ev.getValue().getValue();
+ return value == null ? "" : value.toString();
+ }
+ }
+ // should not happen since tldr and description are mandatory
+ return "";
+ }
+}
diff --git a/pom.xml b/pom.xml
index 2afcd8448a..69524810b7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,6 +36,7 @@
sample-operators
caffeine-bounded-cache-support
bootstrapper-maven-plugin
+ operator-annotations