diff --git a/CHANGELOG.md b/CHANGELOG.md
index c0bf154958..e4cf20c13d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@
#### New Features
* Fix #7385: Support for Kubernetes v1.35 (Timbernetes)
+* New graalvm-metadata-generator-plugin Maven plugin to automatically generate GraalVM native-image reflection metadata from Jandex indexes
#### _**Note**_: Breaking changes
diff --git a/graalvm-metadata-generator-plugin/pom.xml b/graalvm-metadata-generator-plugin/pom.xml
new file mode 100644
index 0000000000..a99353e85f
--- /dev/null
+++ b/graalvm-metadata-generator-plugin/pom.xml
@@ -0,0 +1,125 @@
+
+
+
+ 4.0.0
+
+ io.fabric8
+ kubernetes-client-project
+ 7.6-SNAPSHOT
+
+
+ graalvm-metadata-generator-plugin
+ maven-plugin
+ Fabric8 :: GraalVM Metadata Generator Maven Plugin
+ Maven plugin to generate GraalVM native-image metadata from Jandex indexes
+
+
+ 11
+ 11
+
+
+
+
+
+ org.apache.maven
+ maven-core
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+
+
+
+
+ io.smallrye
+ jandex
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+
+ graalvm-metadata
+
+
+
+ default-descriptor
+ process-classes
+
+
+ help-goal
+
+ helpmojo
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-invoker-plugin
+
+ ${project.build.directory}/it
+ true
+ src/it/settings.xml
+ ${project.build.directory}/local-repo
+ verify
+ true
+ ${skipTests}
+ true
+
+ ${jandex.version}
+ ${jandex.version}
+ ${jackson.version}
+
+
+
+
+ integration-test
+
+ install
+ run
+
+
+
+
+
+
+
diff --git a/graalvm-metadata-generator-plugin/src/it/settings.xml b/graalvm-metadata-generator-plugin/src/it/settings.xml
new file mode 100644
index 0000000000..fc2dff7a0e
--- /dev/null
+++ b/graalvm-metadata-generator-plugin/src/it/settings.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+ it-repo
+
+ true
+
+
+
+ local.central
+ @localRepositoryUrl@
+
+ true
+
+
+ true
+
+
+
+
+
+ local.central
+ @localRepositoryUrl@
+
+ true
+
+
+ true
+
+
+
+
+
+
diff --git a/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/pom.xml b/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/pom.xml
new file mode 100644
index 0000000000..47d72bb886
--- /dev/null
+++ b/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/pom.xml
@@ -0,0 +1,70 @@
+
+
+
+ 4.0.0
+
+ io.fabric8.it
+ simple-jackson-project
+ 1.0-SNAPSHOT
+
+
+ 11
+ 11
+ UTF-8
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ @jackson.version@
+
+
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+ @jandex-maven-plugin.version@
+
+
+
+ jandex
+
+
+
+
+
+ io.fabric8
+ graalvm-metadata-generator-plugin
+ @project.version@
+
+
+
+ generate
+
+
+
+
+
+
+
diff --git a/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/src/main/java/io/fabric8/test/Person.java b/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/src/main/java/io/fabric8/test/Person.java
new file mode 100644
index 0000000000..6d3f6c6b2e
--- /dev/null
+++ b/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/src/main/java/io/fabric8/test/Person.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * 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 io.fabric8.test;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Person {
+
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("age")
+ private int age;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+}
diff --git a/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/verify.groovy b/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/verify.groovy
new file mode 100644
index 0000000000..74858ccd33
--- /dev/null
+++ b/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/verify.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * 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.
+ */
+import groovy.json.JsonSlurper
+
+def configFile = new File(basedir, 'target/classes/META-INF/native-image/io.fabric8/simple-jackson-project/reflect-config.json')
+
+assert configFile.exists() : "reflect-config.json should exist"
+
+def json = new JsonSlurper().parse(configFile)
+
+assert json.size() == 1 : "Should have 1 reflection entry"
+
+def personEntry = json.find { it.name == 'io.fabric8.test.Person' }
+assert personEntry != null : "Should have entry for Person class"
+assert personEntry.condition != null : "Should have condition"
+assert personEntry.condition.typeReachable == 'io.fabric8.test.Person' : "Should have typeReachable condition"
+assert personEntry.allDeclaredConstructors == true : "Should enable all declared constructors"
+assert personEntry.allDeclaredMethods == true : "Should enable all declared methods"
+assert personEntry.allDeclaredFields == true : "Should enable all declared fields"
+
+println "Integration test passed: Jackson annotations detected and configuration generated"
diff --git a/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/GraalVmMetadataGeneratorMojo.java b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/GraalVmMetadataGeneratorMojo.java
new file mode 100644
index 0000000000..b3d0015f0c
--- /dev/null
+++ b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/GraalVmMetadataGeneratorMojo.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * 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 io.fabric8.graalvm.plugin;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Maven Mojo for generating GraalVM native-image metadata from Jandex indexes.
+ */
+@Mojo(name = "generate", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true)
+public class GraalVmMetadataGeneratorMojo extends AbstractMojo {
+
+ @Parameter(defaultValue = "${project}", readonly = true, required = true)
+ private MavenProject project;
+
+ /**
+ * Output directory for generated reflect-config.json.
+ * Default: ${project.build.outputDirectory}/META-INF/native-image/io.fabric8/${project.artifactId}
+ */
+ @Parameter(property = "graalvm.metadata.outputDirectory")
+ private File outputDirectory;
+
+ /**
+ * Strategy for determining which classes to include in reflection configuration.
+ */
+ @Parameter(property = "graalvm.metadata.inclusionStrategy", defaultValue = "JACKSON_ANNOTATIONS")
+ private InclusionStrategy inclusionStrategy;
+
+ /**
+ * Package patterns to include (supports wildcards, e.g., "io.fabric8.*").
+ * These are additive to the inclusion strategy.
+ */
+ @Parameter(property = "graalvm.metadata.includePatterns")
+ private List includePatterns;
+
+ /**
+ * Package patterns to exclude (supports wildcards, e.g., "*.internal.*").
+ */
+ @Parameter(property = "graalvm.metadata.excludePatterns")
+ private List excludePatterns;
+
+ /**
+ * Merge with existing reflect-config.json if it exists.
+ */
+ @Parameter(property = "graalvm.metadata.mergeWithExisting", defaultValue = "true")
+ private boolean mergeWithExisting;
+
+ /**
+ * Skip execution of this plugin.
+ */
+ @Parameter(property = "graalvm.metadata.skip", defaultValue = "false")
+ private boolean skip;
+
+ /**
+ * Enable reflection for all declared constructors.
+ */
+ @Parameter(property = "graalvm.metadata.allDeclaredConstructors", defaultValue = "true")
+ private boolean allDeclaredConstructors;
+
+ /**
+ * Enable reflection for all declared methods.
+ */
+ @Parameter(property = "graalvm.metadata.allDeclaredMethods", defaultValue = "true")
+ private boolean allDeclaredMethods;
+
+ /**
+ * Enable reflection for all declared fields.
+ */
+ @Parameter(property = "graalvm.metadata.allDeclaredFields", defaultValue = "true")
+ private boolean allDeclaredFields;
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ if (skip) {
+ getLog().info("Skipping GraalVM metadata generation (skip=true)");
+ return;
+ }
+
+ try {
+ // Determine output directory
+ if (outputDirectory == null) {
+ String artifactId = project.getArtifactId();
+ outputDirectory = new File(project.getBuild().getOutputDirectory(),
+ "META-INF/native-image/io.fabric8/" + artifactId);
+ }
+
+ File outputFile = new File(outputDirectory, "reflect-config.json");
+ getLog().info("Generating GraalVM reflection configuration: " + outputFile);
+ getLog().info("Inclusion strategy: " + inclusionStrategy);
+
+ // Locate Jandex index
+ File classesDirectory = new File(project.getBuild().getOutputDirectory());
+ File indexFile = new File(classesDirectory, "META-INF/jandex.idx");
+
+ if (!indexFile.exists()) {
+ getLog().warn("Jandex index not found at " + indexFile);
+ getLog().warn("Consider adding jandex-maven-plugin to your build");
+ getLog().warn("Skipping metadata generation");
+ return;
+ }
+
+ // Scan classes
+ JandexReflectionScanner scanner = new JandexReflectionScanner(indexFile);
+ Set classes = scanner.findClasses(inclusionStrategy, includePatterns, excludePatterns);
+
+ if (classes.isEmpty()) {
+ getLog().info("No classes found matching inclusion criteria");
+ return;
+ }
+
+ getLog().info("Found " + classes.size() + " classes for reflection configuration");
+
+ // Generate configuration
+ ReflectionConfigGenerator generator = new ReflectionConfigGenerator(
+ allDeclaredConstructors,
+ allDeclaredMethods,
+ allDeclaredFields);
+
+ generator.generate(classes, outputFile, mergeWithExisting);
+
+ getLog().info("Successfully generated reflection configuration at: " + outputFile);
+
+ } catch (IOException e) {
+ throw new MojoExecutionException("Failed to generate GraalVM metadata", e);
+ }
+ }
+}
diff --git a/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/InclusionStrategy.java b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/InclusionStrategy.java
new file mode 100644
index 0000000000..8ca6268fa3
--- /dev/null
+++ b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/InclusionStrategy.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * 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 io.fabric8.graalvm.plugin;
+
+/**
+ * Strategy for determining which classes should be included in GraalVM reflection configuration.
+ */
+public enum InclusionStrategy {
+ /**
+ * Include classes with Jackson annotations (@JsonDeserialize, @JsonProperty, @JsonSerialize, etc.)
+ */
+ JACKSON_ANNOTATIONS,
+
+ /**
+ * Include classes with Sundrio @Buildable annotation and generated builder classes
+ */
+ SUNDRIO_BUILDERS,
+
+ /**
+ * Include Kubernetes resource classes (HasMetadata implementations)
+ */
+ KUBERNETES_RESOURCES,
+
+ /**
+ * Combination of JACKSON_ANNOTATIONS, SUNDRIO_BUILDERS, and KUBERNETES_RESOURCES
+ */
+ COMPREHENSIVE,
+
+ /**
+ * Only use include/exclude patterns (no annotation-based detection)
+ */
+ PATTERN_BASED,
+
+ /**
+ * Include all public classes (use with caution - generates large config files)
+ */
+ ALL_PUBLIC_CLASSES,
+
+ /**
+ * Include only classes that directly extend java.lang.Object
+ */
+ DIRECT_OBJECT_SUBCLASSES
+}
diff --git a/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/JandexReflectionScanner.java b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/JandexReflectionScanner.java
new file mode 100644
index 0000000000..38f23bf7e6
--- /dev/null
+++ b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/JandexReflectionScanner.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * 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 io.fabric8.graalvm.plugin;
+
+import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.DotName;
+import org.jboss.jandex.IndexReader;
+import org.jboss.jandex.IndexView;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * Scans classes using Jandex index to find classes that require reflection in GraalVM native-image.
+ */
+public class JandexReflectionScanner {
+
+ private static final DotName JSON_DESERIALIZE = DotName
+ .createSimple("com.fasterxml.jackson.databind.annotation.JsonDeserialize");
+ private static final DotName JSON_PROPERTY = DotName.createSimple("com.fasterxml.jackson.annotation.JsonProperty");
+ private static final DotName JSON_SERIALIZE = DotName.createSimple("com.fasterxml.jackson.databind.annotation.JsonSerialize");
+ private static final DotName JSON_TYPE_INFO = DotName.createSimple("com.fasterxml.jackson.annotation.JsonTypeInfo");
+ private static final DotName JSON_SUB_TYPES = DotName.createSimple("com.fasterxml.jackson.annotation.JsonSubTypes");
+ private static final DotName BUILDABLE = DotName.createSimple("io.sundr.builder.annotations.Buildable");
+ private static final DotName HAS_METADATA = DotName.createSimple("io.fabric8.kubernetes.api.model.HasMetadata");
+
+ private final IndexView index;
+
+ public JandexReflectionScanner(File indexFile) throws IOException {
+ try (InputStream in = new FileInputStream(indexFile)) {
+ IndexReader reader = new IndexReader(in);
+ this.index = reader.read();
+ }
+ }
+
+ /**
+ * Find classes that match the given inclusion strategy.
+ */
+ public Set findClasses(InclusionStrategy strategy, List includePatterns, List excludePatterns) {
+ Set classes = new HashSet<>();
+
+ switch (strategy) {
+ case JACKSON_ANNOTATIONS:
+ classes.addAll(findJacksonAnnotatedClasses());
+ break;
+ case SUNDRIO_BUILDERS:
+ classes.addAll(findSundrioBuilders());
+ break;
+ case KUBERNETES_RESOURCES:
+ classes.addAll(findKubernetesResources());
+ break;
+ case COMPREHENSIVE:
+ classes.addAll(findJacksonAnnotatedClasses());
+ classes.addAll(findSundrioBuilders());
+ classes.addAll(findKubernetesResources());
+ break;
+ case PATTERN_BASED:
+ // Only use patterns, don't scan for annotations
+ break;
+ case ALL_PUBLIC_CLASSES:
+ classes.addAll(findAllPublicClasses());
+ break;
+ case DIRECT_OBJECT_SUBCLASSES:
+ classes.addAll(findDirectObjectSubclasses());
+ break;
+ }
+
+ // Apply include patterns
+ if (includePatterns != null && !includePatterns.isEmpty()) {
+ Set matchedByPatterns = findByPatterns(includePatterns);
+ if (strategy == InclusionStrategy.PATTERN_BASED) {
+ classes.addAll(matchedByPatterns);
+ } else {
+ // For other strategies, patterns are additive
+ classes.addAll(matchedByPatterns);
+ }
+ }
+
+ // Apply exclude patterns
+ if (excludePatterns != null && !excludePatterns.isEmpty()) {
+ Set excluded = findByPatterns(excludePatterns);
+ classes.removeAll(excluded);
+ }
+
+ return classes;
+ }
+
+ /**
+ * Find classes with Jackson annotations.
+ */
+ private Set findJacksonAnnotatedClasses() {
+ Set classes = new HashSet<>();
+
+ // Find classes with class-level annotations
+ addClassesWithClassAnnotation(classes, JSON_DESERIALIZE);
+ addClassesWithClassAnnotation(classes, JSON_SERIALIZE);
+ addClassesWithClassAnnotation(classes, JSON_TYPE_INFO);
+ addClassesWithClassAnnotation(classes, JSON_SUB_TYPES);
+
+ // Find classes with member-level annotations
+ addClassesWithMemberAnnotation(classes, JSON_PROPERTY);
+
+ return classes;
+ }
+
+ /**
+ * Add classes that have the specified annotation at the class level.
+ */
+ private void addClassesWithClassAnnotation(Set classes, DotName annotationName) {
+ for (AnnotationInstance annotation : index.getAnnotations(annotationName)) {
+ if (annotation.target().kind() == org.jboss.jandex.AnnotationTarget.Kind.CLASS) {
+ classes.add(annotation.target().asClass().name().toString());
+ }
+ }
+ }
+
+ /**
+ * Add classes that have the specified annotation on fields or methods.
+ */
+ private void addClassesWithMemberAnnotation(Set classes, DotName annotationName) {
+ for (AnnotationInstance annotation : index.getAnnotations(annotationName)) {
+ org.jboss.jandex.AnnotationTarget.Kind kind = annotation.target().kind();
+ if (kind == org.jboss.jandex.AnnotationTarget.Kind.FIELD) {
+ classes.add(annotation.target().asField().declaringClass().name().toString());
+ } else if (kind == org.jboss.jandex.AnnotationTarget.Kind.METHOD) {
+ classes.add(annotation.target().asMethod().declaringClass().name().toString());
+ }
+ }
+ }
+
+ /**
+ * Find classes with Sundrio @Buildable annotation and generated builder classes.
+ */
+ private Set findSundrioBuilders() {
+ Set classes = new HashSet<>();
+
+ for (AnnotationInstance annotation : index.getAnnotations(BUILDABLE)) {
+ if (annotation.target().kind() == org.jboss.jandex.AnnotationTarget.Kind.CLASS) {
+ String className = annotation.target().asClass().name().toString();
+ classes.add(className);
+
+ // Add generated builder class
+ String builderClassName = className + "Builder";
+ if (index.getClassByName(DotName.createSimple(builderClassName)) != null) {
+ classes.add(builderClassName);
+ }
+ }
+ }
+
+ return classes;
+ }
+
+ /**
+ * Find Kubernetes resource classes (HasMetadata implementations).
+ */
+ @SuppressWarnings("deprecation")
+ private Set findKubernetesResources() {
+ Set classes = new HashSet<>();
+
+ // Using getAllKnownImplementors which is deprecated in Jandex 3.x but still functional
+ for (ClassInfo classInfo : index.getAllKnownImplementors(HAS_METADATA)) {
+ classes.add(classInfo.name().toString());
+ }
+
+ return classes;
+ }
+
+ /**
+ * Find all public classes.
+ */
+ private Set findAllPublicClasses() {
+ Set classes = new HashSet<>();
+
+ for (ClassInfo classInfo : index.getKnownClasses()) {
+ if (java.lang.reflect.Modifier.isPublic(classInfo.flags())) {
+ classes.add(classInfo.name().toString());
+ }
+ }
+
+ return classes;
+ }
+
+ /**
+ * Find classes that directly extend java.lang.Object (no other superclass).
+ */
+ private Set findDirectObjectSubclasses() {
+ Set classes = new HashSet<>();
+ DotName objectName = DotName.createSimple("java.lang.Object");
+
+ for (ClassInfo classInfo : index.getKnownClasses()) {
+ // Skip interfaces and annotations
+ if (classInfo.isInterface() || classInfo.isAnnotation()) {
+ continue;
+ }
+
+ // Get the superclass
+ DotName superClass = classInfo.superName();
+
+ // Include only if superclass is java.lang.Object
+ if (superClass != null && superClass.equals(objectName)) {
+ classes.add(classInfo.name().toString());
+ }
+ }
+
+ return classes;
+ }
+
+ /**
+ * Find classes matching the given patterns.
+ */
+ private Set findByPatterns(List patterns) {
+ Set classes = new HashSet<>();
+
+ if (patterns == null || patterns.isEmpty()) {
+ return classes;
+ }
+
+ List compiledPatterns = patterns.stream()
+ .map(p -> Pattern.compile(p.replace(".", "\\.").replace("*", ".*")))
+ .collect(java.util.stream.Collectors.toList());
+
+ for (ClassInfo classInfo : index.getKnownClasses()) {
+ String className = classInfo.name().toString();
+ for (Pattern pattern : compiledPatterns) {
+ if (pattern.matcher(className).matches()) {
+ classes.add(className);
+ break;
+ }
+ }
+ }
+
+ return classes;
+ }
+}
diff --git a/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/ReflectionConfigGenerator.java b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/ReflectionConfigGenerator.java
new file mode 100644
index 0000000000..981f700059
--- /dev/null
+++ b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/ReflectionConfigGenerator.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * 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 io.fabric8.graalvm.plugin;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Generates GraalVM reflect-config.json files.
+ */
+public class ReflectionConfigGenerator {
+
+ private final ObjectMapper objectMapper;
+ private final boolean allDeclaredConstructors;
+ private final boolean allDeclaredMethods;
+ private final boolean allDeclaredFields;
+
+ public ReflectionConfigGenerator(boolean allDeclaredConstructors, boolean allDeclaredMethods,
+ boolean allDeclaredFields) {
+ this.allDeclaredConstructors = allDeclaredConstructors;
+ this.allDeclaredMethods = allDeclaredMethods;
+ this.allDeclaredFields = allDeclaredFields;
+
+ this.objectMapper = new ObjectMapper();
+ this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
+ }
+
+ /**
+ * Generate reflection configuration and write to file.
+ *
+ * @param classes Classes to include in reflection configuration
+ * @param outputFile Output file path
+ * @param mergeWithExisting Whether to merge with existing configuration
+ * @throws IOException if file operations fail
+ */
+ public void generate(Set classes, File outputFile, boolean mergeWithExisting) throws IOException {
+ Map entries = new LinkedHashMap<>();
+
+ // Load existing configuration if merging
+ if (mergeWithExisting && outputFile.exists()) {
+ List existing = loadExisting(outputFile);
+ for (ReflectionEntry entry : existing) {
+ entries.put(entry.getName(), entry);
+ }
+ }
+
+ // Add new classes
+ for (String className : classes) {
+ entries.computeIfAbsent(className, key -> {
+ ReflectionEntry entry = new ReflectionEntry(key);
+ entry.setCondition(new ReflectionEntry.Condition(key));
+ entry.setAllDeclaredConstructors(allDeclaredConstructors);
+ entry.setAllDeclaredMethods(allDeclaredMethods);
+ entry.setAllDeclaredFields(allDeclaredFields);
+ return entry;
+ });
+ }
+
+ // Sort by class name for stable output
+ List sortedEntries = new ArrayList<>(entries.values());
+ sortedEntries.sort(Comparator.comparing(ReflectionEntry::getName));
+
+ // Ensure output directory exists
+ File outputDir = outputFile.getParentFile();
+ if (outputDir != null && !outputDir.exists()) {
+ outputDir.mkdirs();
+ }
+
+ // Write to file
+ objectMapper.writeValue(outputFile, sortedEntries);
+ }
+
+ /**
+ * Load existing reflection configuration from file.
+ */
+ private List loadExisting(File file) throws IOException {
+ ReflectionEntry[] entries = objectMapper.readValue(file, ReflectionEntry[].class);
+ return java.util.Arrays.asList(entries);
+ }
+}
diff --git a/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/ReflectionEntry.java b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/ReflectionEntry.java
new file mode 100644
index 0000000000..46429c53aa
--- /dev/null
+++ b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/ReflectionEntry.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * 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 io.fabric8.graalvm.plugin;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Objects;
+
+/**
+ * Represents a single entry in GraalVM's reflect-config.json file.
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class ReflectionEntry {
+
+ @JsonProperty("condition")
+ private Condition condition;
+
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("allDeclaredConstructors")
+ private Boolean allDeclaredConstructors;
+
+ @JsonProperty("allDeclaredMethods")
+ private Boolean allDeclaredMethods;
+
+ @JsonProperty("allDeclaredFields")
+ private Boolean allDeclaredFields;
+
+ /**
+ * Nested class representing the condition for reflection metadata.
+ */
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public static class Condition {
+ @JsonProperty("typeReachable")
+ private String typeReachable;
+
+ public Condition() {
+ }
+
+ public Condition(String typeReachable) {
+ this.typeReachable = typeReachable;
+ }
+
+ public String getTypeReachable() {
+ return typeReachable;
+ }
+
+ public void setTypeReachable(String typeReachable) {
+ this.typeReachable = typeReachable;
+ }
+ }
+
+ public ReflectionEntry() {
+ }
+
+ public ReflectionEntry(String name) {
+ this.name = name;
+ }
+
+ public Condition getCondition() {
+ return condition;
+ }
+
+ public void setCondition(Condition condition) {
+ this.condition = condition;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Boolean getAllDeclaredConstructors() {
+ return allDeclaredConstructors;
+ }
+
+ public void setAllDeclaredConstructors(Boolean allDeclaredConstructors) {
+ this.allDeclaredConstructors = allDeclaredConstructors;
+ }
+
+ public Boolean getAllDeclaredMethods() {
+ return allDeclaredMethods;
+ }
+
+ public void setAllDeclaredMethods(Boolean allDeclaredMethods) {
+ this.allDeclaredMethods = allDeclaredMethods;
+ }
+
+ public Boolean getAllDeclaredFields() {
+ return allDeclaredFields;
+ }
+
+ public void setAllDeclaredFields(Boolean allDeclaredFields) {
+ this.allDeclaredFields = allDeclaredFields;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ ReflectionEntry that = (ReflectionEntry) o;
+ return Objects.equals(name, that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name);
+ }
+
+ @Override
+ public String toString() {
+ return "ReflectionEntry{" +
+ "name='" + name + '\'' +
+ ", allDeclaredConstructors=" + allDeclaredConstructors +
+ ", allDeclaredMethods=" + allDeclaredMethods +
+ ", allDeclaredFields=" + allDeclaredFields +
+ '}';
+ }
+}
diff --git a/pom.xml b/pom.xml
index 54c7ad62a5..82a52673cc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -232,6 +232,7 @@
log4j
kubernetes-examples
bom-generator-plugin
+ graalvm-metadata-generator-plugin
@@ -1504,5 +1505,31 @@
+
+ generate-graal-metadata
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ io.fabric8
+ graalvm-metadata-generator-plugin
+ ${project.version}
+
+
+
+ generate
+
+
+ JACKSON_ANNOTATIONS
+
+
+
+
+
+
+