diff --git a/src/main/java/org/openrewrite/java/spring/data/search/FindMissingMongoValueRepresentation.java b/src/main/java/org/openrewrite/java/spring/data/search/FindMissingMongoValueRepresentation.java
new file mode 100644
index 000000000..8a222cb4f
--- /dev/null
+++ b/src/main/java/org/openrewrite/java/spring/data/search/FindMissingMongoValueRepresentation.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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 org.openrewrite.java.spring.data.search;
+
+import lombok.Getter;
+import lombok.Value;
+import lombok.With;
+import org.jspecify.annotations.Nullable;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.ScanningRecipe;
+import org.openrewrite.SourceFile;
+import org.openrewrite.Tree;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.java.marker.JavaProject;
+import org.openrewrite.java.spring.table.MongoValueRepresentationFields;
+import org.openrewrite.marker.Marker;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * Find explicitly MongoDB-mapped UUID and big-number fields for which Spring Data MongoDB 5
+ * no longer supplies a default representation.
+ */
+public class FindMissingMongoValueRepresentation extends ScanningRecipe {
+
+ static final String UUID_PROPERTY = "spring.mongodb.representation.uuid";
+ static final String BIG_NUMBER_PROPERTY = "spring.data.mongodb.representation.big-decimal";
+ // The value a suggested-but-not-yet-chosen property is created with. Must be a real, validly
+ // bindable enum constant (not placeholder text like "") so a project that never
+ // gets its suggestion followed up on still starts up correctly — it's simply as unconfigured as
+ // it was before the recipe ran, and gets the same treatment as a user writing UNSPECIFIED
+ // themselves (see ValueKind.isConfiguredValue, which deliberately excludes it).
+ static final String UNSPECIFIED_VALUE = "UNSPECIFIED";
+ static final String UUID_TYPE = "java.util.UUID";
+ static final String BIG_DECIMAL_TYPE = "java.math.BigDecimal";
+ static final String BIG_INTEGER_TYPE = "java.math.BigInteger";
+
+ private static final String UUID_INVALID_PROPERTY_MESSAGE =
+ "`" + UUID_PROPERTY + "` needs a concrete UUID representation matching the existing BSON data.";
+
+ private static final String BIG_NUMBER_INVALID_PROPERTY_MESSAGE =
+ "`" + BIG_NUMBER_PROPERTY + "` needs a concrete big-number representation matching the existing BSON data.";
+
+ final transient MongoValueRepresentationFields affectedFields = new MongoValueRepresentationFields(this);
+
+ @Getter
+ final String displayName = "Find missing MongoDB value representation configuration";
+
+ @Getter
+ final String description = "Find explicitly MongoDB-mapped UUID, BigInteger, and BigDecimal fields that require an " +
+ "explicit representation when migrating to Spring Data MongoDB 5. The recipe reports affected fields " +
+ "without choosing a storage representation.";
+
+ /**
+ * A baseline configuration file generated by {@link #generate} is necessarily empty the cycle
+ * it's created — the scan that fed that cycle's {@link Accumulator} predates the file's own
+ * existence, so it can't yet be recognised as the project's preferred configuration source. A
+ * further cycle is required to scan the now-persisted file and populate it. Without opting in
+ * here, a real (non-test) run stops after the generating cycle, since the default {@code false}
+ * only accounts for cycles requested by other recipes.
+ */
+ @Override
+ public boolean causesAnotherCycle() {
+ return true;
+ }
+
+ enum ValueKind {
+ // Mirrors org.bson.UuidRepresentation, excluding UNSPECIFIED (not a valid choice).
+ UUID("UUID", UUID_PROPERTY, UUID_INVALID_PROPERTY_MESSAGE,
+ "standard", "java-legacy", "c-sharp-legacy", "python-legacy"),
+ // Mirrors Spring Data MongoDB's BigDecimalRepresentation, excluding UNSPECIFIED.
+ BIG_NUMBER("BigDecimal/BigInteger", BIG_NUMBER_PROPERTY,
+ BIG_NUMBER_INVALID_PROPERTY_MESSAGE, "string", "decimal128");
+
+ final String displayName;
+ final String configurationProperty;
+ final String invalidPropertyMessage;
+ private final Set supportedValues;
+
+ ValueKind(String displayName, String configurationProperty, String invalidPropertyMessage,
+ String... supportedValues) {
+ this.displayName = displayName;
+ this.configurationProperty = configurationProperty;
+ this.invalidPropertyMessage = invalidPropertyMessage;
+ this.supportedValues = new HashSet<>();
+ for (String supportedValue : supportedValues) {
+ this.supportedValues.add(normalize(supportedValue));
+ }
+ }
+
+ boolean isConfiguredValue(@Nullable String value) {
+ if (value == null) {
+ return false;
+ }
+ String candidate = value.trim();
+ if (candidate.startsWith("${") && candidate.endsWith("}")) {
+ return true;
+ }
+ return supportedValues.contains(normalize(candidate));
+ }
+
+ private static String normalize(String value) {
+ return value.replace("-", "")
+ .replace("_", "")
+ .replace(" ", "")
+ .toUpperCase(Locale.ROOT);
+ }
+ }
+
+ @Value
+ static class Occurrence {
+ Path sourcePath;
+ UUID owningClassId;
+ String owningType;
+ String field;
+ ValueKind kind;
+ }
+
+ @Value
+ static class ConfigurationIssue {
+ Path sourcePath;
+ UUID treeId;
+ ValueKind kind;
+ }
+
+ public static class Accumulator {
+ final Map> validlyConfigured = newProjectSetsByKind();
+ final Map> propertyAttempted = newProjectSetsByKind();
+ final Map> javaAttempted = newProjectSetsByKind();
+
+ // Populated only by scanning for a ProjectDiagnostic marker already present in the tree
+ // (left there by a *prior* cycle's edit) — Accumulator itself is rebuilt from scratch every
+ // cycle, so this is the only way "already fully handled" survives across cycles.
+ final Set finalizedProjects = ConcurrentHashMap.newKeySet();
+
+ // Guards against duplicate data-table rows both within a cycle and across cycles: seeded
+ // from a RowsRecorded marker (see FindMissingMongoValueRepresentation.generate()) whenever
+ // a baseline configuration file generated in an earlier cycle is rescanned.
+ final Set rowsInsertedProjects = ConcurrentHashMap.newKeySet();
+
+ // The single winning path per project, chosen via SpringConfigFileSupport.preferredConfigurationSource
+ // — not a list of every config file found.
+ final Map preferredConfigurationSource = new ConcurrentHashMap<>();
+
+ final Map> occurrences = new ConcurrentHashMap<>();
+
+ // Covers both properties/YAML entries with an unsupported value and Java configuration
+ // calls with an invalid argument (e.g., uuidRepresentation(null)) — the same record type
+ // serves both, since only the source path and kind differ.
+ final Map> configurationIssues =
+ new ConcurrentHashMap<>();
+
+ void markConfigured(ValueKind kind, JavaProject project) {
+ projectsFor(validlyConfigured, kind).add(project);
+ }
+
+ boolean isUnconfigured(ValueKind kind, JavaProject project) {
+ return !projectsFor(validlyConfigured, kind).contains(project);
+ }
+
+ void markPropertyAttempted(ValueKind kind, JavaProject project) {
+ projectsFor(propertyAttempted, kind).add(project);
+ }
+
+ boolean isPropertyUnattempted(ValueKind kind, JavaProject project) {
+ return !projectsFor(propertyAttempted, kind).contains(project);
+ }
+
+ void markJavaAttempted(ValueKind kind, JavaProject project) {
+ projectsFor(javaAttempted, kind).add(project);
+ }
+
+ boolean isJavaUnattempted(ValueKind kind, JavaProject project) {
+ return !projectsFor(javaAttempted, kind).contains(project);
+ }
+
+ void addOccurrence(JavaProject project, Occurrence occurrence) {
+ occurrences.computeIfAbsent(project, ignored -> new ConcurrentLinkedQueue<>()).add(occurrence);
+ }
+
+ void addConfigurationIssue(JavaProject project, ConfigurationIssue issue) {
+ configurationIssues.computeIfAbsent(project, ignored -> new ConcurrentLinkedQueue<>()).add(issue);
+ }
+
+ private static Map> newProjectSetsByKind() {
+ Map> byKind = new EnumMap<>(ValueKind.class);
+ for (ValueKind kind : ValueKind.values()) {
+ byKind.put(kind, ConcurrentHashMap.newKeySet());
+ }
+ return byKind;
+ }
+
+ // newProjectSetsByKind() always populates every ValueKind, so the lookup can never miss.
+ private static Set projectsFor(Map> byKind, ValueKind kind) {
+ return Objects.requireNonNull(byKind.get(kind));
+ }
+ }
+
+ @Override
+ public Accumulator getInitialValue(ExecutionContext ctx) {
+ return new Accumulator();
+ }
+
+ @Override
+ public TreeVisitor, ExecutionContext> getScanner(Accumulator acc) {
+ return new TreeVisitor() {
+ @Override
+ public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
+ if (!(tree instanceof SourceFile)) {
+ return tree;
+ }
+ SourceFile source = (SourceFile) tree;
+ JavaProject project = SpringConfigFileSupport.javaProject(source);
+ if (project != null) {
+ MongoValueRepresentationScanner.scan(source, project, acc, ctx);
+ }
+ return source;
+ }
+ };
+ }
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor(Accumulator acc) {
+ return new TreeVisitor() {
+ @Override
+ public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
+ if (!(tree instanceof SourceFile)) {
+ return tree;
+ }
+ SourceFile source = (SourceFile) tree;
+ JavaProject project = SpringConfigFileSupport.javaProject(source);
+ return project == null ? source : MongoValueRepresentationDiagnostics.apply(
+ FindMissingMongoValueRepresentation.this, source, project, acc, ctx);
+ }
+ };
+ }
+
+ /**
+ * A project with affected fields but no Spring configuration file gets a baseline
+ * {@code application.properties} generated (path derived from an existing occurrence's own
+ * {@code src/main/java} location), so every affected field gets the same suggested-property
+ * treatment instead of an arbitrary class being singled out. The file is picked up by the
+ * scanner on the following cycle, the same as any pre-existing configuration file.
+ */
+ @Override
+ public Collection extends SourceFile> generate(Accumulator acc, ExecutionContext ctx) {
+ List generated = new ArrayList<>();
+ for (JavaProject project : acc.occurrences.keySet()) {
+ SourceFile baseline = MongoValueRepresentationDiagnostics.generateBaselineConfiguration(project, acc, ctx);
+ if (baseline != null) {
+ generated.add(baseline);
+ }
+ }
+ return generated;
+ }
+
+ @Value
+ @With
+ static class ProjectDiagnostic implements Marker {
+ UUID id;
+ }
+
+ /**
+ * Attached to a freshly generated baseline configuration file, so a later scan (once the
+ * generated file has been persisted into the tree) knows its project's data table rows were
+ * already recorded, even though {@link Accumulator} itself doesn't survive across cycles.
+ */
+ @Value
+ @With
+ static class RowsRecorded implements Marker {
+ UUID id;
+ }
+}
diff --git a/src/main/java/org/openrewrite/java/spring/data/search/MongoValueRepresentationDiagnostics.java b/src/main/java/org/openrewrite/java/spring/data/search/MongoValueRepresentationDiagnostics.java
new file mode 100644
index 000000000..0e8681f6d
--- /dev/null
+++ b/src/main/java/org/openrewrite/java/spring/data/search/MongoValueRepresentationDiagnostics.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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 org.openrewrite.java.spring.data.search;
+
+import org.jspecify.annotations.Nullable;
+import org.openrewrite.Cursor;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.SourceFile;
+import org.openrewrite.Tree;
+import org.openrewrite.java.JavaIsoVisitor;
+import org.openrewrite.java.marker.JavaProject;
+import org.openrewrite.java.spring.AddSpringProperty;
+import org.openrewrite.java.spring.table.MongoValueRepresentationFields;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.JavaSourceFile;
+import org.openrewrite.properties.PropertiesIsoVisitor;
+import org.openrewrite.properties.PropertiesParser;
+import org.openrewrite.properties.tree.Properties;
+import org.openrewrite.trait.Comments;
+import org.openrewrite.yaml.YamlIsoVisitor;
+import org.openrewrite.yaml.tree.Yaml;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import static org.openrewrite.java.spring.data.search.FindMissingMongoValueRepresentation.*;
+
+final class MongoValueRepresentationDiagnostics {
+
+ private MongoValueRepresentationDiagnostics() {
+ }
+
+ static SourceFile apply(FindMissingMongoValueRepresentation recipe, SourceFile source, JavaProject project,
+ Accumulator acc, ExecutionContext ctx) {
+ if (acc.finalizedProjects.contains(project)) {
+ return source;
+ }
+ ProjectDiagnosis diagnosis = diagnose(project, acc);
+ if (diagnosis == null) {
+ return source;
+ }
+ insertRowsOnce(recipe, project, diagnosis.actionable, acc, ctx);
+
+ if (source instanceof JavaSourceFile) {
+ return applyToJavaConfiguration((JavaSourceFile) source, project, diagnosis, acc, ctx);
+ }
+ if (diagnosis.preferredConfiguration == null ||
+ (!(source instanceof Properties.File) && !(source instanceof Yaml.Documents))) {
+ return source;
+ }
+ return applyToConfigurationFile(source, project, diagnosis, acc, ctx);
+ }
+
+ /**
+ * Marks any faulty Java configuration calls (e.g. {@code uuidRepresentation(null)}) in place,
+ * rather than leaving them to be silently shadowed by a properties-file suggestion for the same
+ * kind.
+ */
+ private static SourceFile applyToJavaConfiguration(JavaSourceFile source, JavaProject project,
+ ProjectDiagnosis diagnosis, Accumulator acc,
+ ExecutionContext ctx) {
+ List issues = configurationIssues(
+ source.getSourcePath(), project, diagnosis.occurrences, acc);
+ if (issues.isEmpty()) {
+ return source;
+ }
+ SourceFile changed = markJavaConfigurationIssues(source, issues, ctx);
+ return finalizeUnless(changed, needsBaselineFile(diagnosis, project, acc));
+ }
+
+ private static SourceFile markJavaConfigurationIssues(JavaSourceFile source, List issues,
+ ExecutionContext ctx) {
+ Map kinds = new HashMap<>();
+ for (ConfigurationIssue issue : issues) {
+ kinds.put(issue.getTreeId(), issue.getKind());
+ }
+ // The flagged invocation itself is left untouched, since wrapping it in a SearchResult
+ // marker would print as literal text ahead of the real statement in a production run,
+ // corrupting it (SearchResult is only kept separate from real output inside the RewriteTest
+ // harness); Comments.of(...) is idempotent, so no separate already-commented guard is needed.
+ return (SourceFile) new JavaIsoVisitor() {
+ @Override
+ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext p) {
+ J.MethodInvocation m = super.visitMethodInvocation(method, p);
+ ValueKind kind = kinds.get(m.getId());
+ return kind == null ? m :
+ Comments.of(new Cursor(getCursor().getParentOrThrow(), m)).comment(" " + kind.invalidPropertyMessage);
+ }
+ }.visitNonNull(source, ctx);
+ }
+
+ /**
+ * Whether some kind still needs a baseline configuration file: unresolved, with no config file
+ * for this project yet, and never attempted in Java either (a faulty Java attempt is handled by
+ * {@link #applyToJavaConfiguration} instead of a competing properties-file suggestion).
+ */
+ private static boolean needsBaselineFile(ProjectDiagnosis diagnosis, JavaProject project, Accumulator acc) {
+ if (diagnosis.preferredConfiguration != null) {
+ return false;
+ }
+ for (Occurrence occurrence : diagnosis.unresolved) {
+ if (acc.isJavaUnattempted(occurrence.getKind(), project)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * A baseline {@code application.properties} for a project that has affected fields but no
+ * Spring configuration file, so every affected field gets the same suggested-property
+ * treatment on a later cycle instead of an arbitrary class being singled out.
+ */
+ static @Nullable SourceFile generateBaselineConfiguration(JavaProject project, Accumulator acc,
+ ExecutionContext ctx) {
+ Path path = baselineConfigurationPath(project, acc);
+ if (path == null) {
+ return null;
+ }
+ Optional parsed = PropertiesParser.builder().build().parse(ctx, "").findFirst();
+ if (!parsed.isPresent()) {
+ return null;
+ }
+ SourceFile brandNewFile = parsed.get().withSourcePath(path);
+ return brandNewFile.withMarkers(brandNewFile.getMarkers()
+ .addIfAbsent(project)
+ .addIfAbsent(new RowsRecorded(Tree.randomId())));
+ }
+
+ private static @Nullable Path baselineConfigurationPath(JavaProject project, Accumulator acc) {
+ ProjectDiagnosis diagnosis = diagnose(project, acc);
+ if (diagnosis == null || !needsBaselineFile(diagnosis, project, acc)) {
+ return null;
+ }
+ ConcurrentLinkedQueue projectOccurrences = acc.occurrences.get(project);
+ if (projectOccurrences == null) {
+ return null;
+ }
+ for (Occurrence occurrence : projectOccurrences) {
+ Path resourcesRoot = resourcesRootFor(occurrence.getSourcePath());
+ if (resourcesRoot != null) {
+ return resourcesRoot.resolve("application.properties");
+ }
+ }
+ return null;
+ }
+
+ private static @Nullable Path resourcesRootFor(Path javaSourcePath) {
+ Path parent = javaSourcePath.getParent();
+ if (parent == null) {
+ return null;
+ }
+ int count = parent.getNameCount();
+ for (int i = 0; i + 2 < count; i++) {
+ if ("src".equals(parent.getName(i).toString()) &&
+ "main".equals(parent.getName(i + 1).toString()) &&
+ "java".equals(parent.getName(i + 2).toString())) {
+ Path resources = Paths.get("src", "main", "resources");
+ return i == 0 ? resources : parent.subpath(0, i).resolve(resources);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * What, if anything, this project needs done: which occurrences still need a configured
+ * representation, and where (if anywhere) a new configuration should be suggested. Independent
+ * of which source file is currently being visited.
+ */
+ private static @Nullable ProjectDiagnosis diagnose(JavaProject project, Accumulator acc) {
+ List occurrences = projectOccurrences(project, acc);
+ if (occurrences.isEmpty()) {
+ return null;
+ }
+ List unresolved = unresolvedOccurrences(occurrences, project, acc);
+ Set invalidKinds = invalidConfigurationKinds(project, acc);
+ List actionable = actionableOccurrences(occurrences, unresolved, invalidKinds);
+ if (actionable.isEmpty()) {
+ return null;
+ }
+ Path preferredConfiguration = acc.preferredConfigurationSource.get(project);
+ return new ProjectDiagnosis(occurrences, unresolved, actionable, preferredConfiguration);
+ }
+
+ private static SourceFile applyToConfigurationFile(SourceFile source, JavaProject project,
+ ProjectDiagnosis diagnosis, Accumulator acc,
+ ExecutionContext ctx) {
+ List issues = configurationIssues(
+ source.getSourcePath(), project, diagnosis.occurrences, acc);
+ boolean preferred = source.getSourcePath().equals(diagnosis.preferredConfiguration);
+ List propertiesToAdd = propertiesToAdd(preferred, diagnosis.unresolved, project, acc);
+ if (issues.isEmpty() && propertiesToAdd.isEmpty()) {
+ return source;
+ }
+
+ SourceFile changed = addSuggestedProperties(source, propertiesToAdd, ctx);
+ if (!issues.isEmpty()) {
+ changed = markConfigurationIssues(changed, issues, ctx);
+ }
+ return changed.withMarkers(changed.getMarkers().addIfAbsent(new ProjectDiagnostic(Tree.randomId())));
+ }
+
+ /**
+ * Attaches the "fully handled" marker unless {@code stillPending} — a baseline configuration
+ * file may still be needed for some other kind, and that generate-then-populate sequence takes
+ * multiple cycles; finalizing here first would permanently stop it from ever completing.
+ */
+ private static SourceFile finalizeUnless(SourceFile changed, boolean stillPending) {
+ return stillPending ? changed :
+ changed.withMarkers(changed.getMarkers().addIfAbsent(new ProjectDiagnostic(Tree.randomId())));
+ }
+
+ private static SourceFile addSuggestedProperties(SourceFile source, List propertiesToAdd,
+ ExecutionContext ctx) {
+ SourceFile changed = source;
+ for (ValueKind kind : propertiesToAdd) {
+ changed = addUnspecifiedPropertySuggestion(changed, kind, ctx);
+ }
+ return changed;
+ }
+
+ private static final class ProjectDiagnosis {
+ final List occurrences;
+ final List unresolved;
+ final List actionable;
+ final @Nullable Path preferredConfiguration;
+
+ ProjectDiagnosis(List occurrences, List unresolved, List actionable,
+ @Nullable Path preferredConfiguration) {
+ this.occurrences = occurrences;
+ this.unresolved = unresolved;
+ this.actionable = actionable;
+ this.preferredConfiguration = preferredConfiguration;
+ }
+ }
+
+ private static List projectOccurrences(JavaProject project, Accumulator acc) {
+ return new ArrayList<>(acc.occurrences.getOrDefault(project, new ConcurrentLinkedQueue<>()));
+ }
+
+ private static List unresolvedOccurrences(List occurrences, JavaProject project,
+ Accumulator acc) {
+ List unresolved = new ArrayList<>();
+ for (Occurrence occurrence : occurrences) {
+ if (acc.isUnconfigured(occurrence.getKind(), project)) {
+ unresolved.add(occurrence);
+ }
+ }
+ return unresolved;
+ }
+
+ private static Set invalidConfigurationKinds(JavaProject project, Accumulator acc) {
+ Set kinds = EnumSet.noneOf(ValueKind.class);
+ for (ConfigurationIssue issue : acc.configurationIssues.getOrDefault(project,
+ new ConcurrentLinkedQueue<>())) {
+ kinds.add(issue.getKind());
+ }
+ return kinds;
+ }
+
+ private static List actionableOccurrences(List occurrences,
+ List unresolved,
+ Set invalidKinds) {
+ if (invalidKinds.isEmpty()) {
+ return unresolved;
+ }
+ // A kind can be configured overall (a valid value exists somewhere) yet still have a
+ // separate invalid entry elsewhere, e.g., a bad profile override alongside a valid default.
+ // Such occurrences aren't in `unresolved`, so invalidKinds is what surfaces them here.
+ Set unresolvedLookup = new HashSet<>(unresolved);
+ List actionable = new ArrayList<>();
+ for (Occurrence occurrence : occurrences) {
+ if (unresolvedLookup.contains(occurrence) || invalidKinds.contains(occurrence.getKind())) {
+ actionable.add(occurrence);
+ }
+ }
+ return actionable;
+ }
+
+ private static List propertiesToAdd(boolean preferred, List unresolved,
+ JavaProject project, Accumulator acc) {
+ List properties = new ArrayList<>();
+ if (preferred) {
+ for (ValueKind kind : ValueKind.values()) {
+ if (hasKind(unresolved, kind) && acc.isPropertyUnattempted(kind, project) && acc.isJavaUnattempted(kind, project)) {
+ properties.add(kind);
+ }
+ }
+ }
+ return properties;
+ }
+
+ private static void insertRowsOnce(FindMissingMongoValueRepresentation recipe, JavaProject project,
+ List occurrences, Accumulator acc, ExecutionContext ctx) {
+ if (!acc.rowsInsertedProjects.add(project)) {
+ return;
+ }
+ for (Occurrence occurrence : occurrences) {
+ recipe.affectedFields.insertRow(ctx, new MongoValueRepresentationFields.Row(
+ occurrence.getSourcePath().toString(), occurrence.getOwningType(), occurrence.getField(),
+ occurrence.getKind().displayName, occurrence.getKind().configurationProperty));
+ }
+ }
+
+ /**
+ * {@link FindMissingMongoValueRepresentation#UNSPECIFIED_VALUE}: a real, validly bindable value
+ * rather than placeholder text, so a project that never gets its suggestion followed up on still
+ * starts up correctly — it's simply as unconfigured as it was before the recipe ran. Uses the
+ * same message an existing invalid value would get from {@link #markConfigurationIssues}, since
+ * that's exactly what this value is once written.
+ */
+ private static SourceFile addUnspecifiedPropertySuggestion(SourceFile source, ValueKind kind, ExecutionContext ctx) {
+ String path = source.getSourcePath().toString().replace('\\', '/');
+ return (SourceFile) new AddSpringProperty(
+ kind.configurationProperty, UNSPECIFIED_VALUE, kind.invalidPropertyMessage, Collections.singletonList(path))
+ .getVisitor().visitNonNull(source, ctx);
+ }
+
+ private static List configurationIssues(Path sourcePath, JavaProject project,
+ List occurrences, Accumulator acc) {
+ List issues = new ArrayList<>();
+ for (ConfigurationIssue issue : acc.configurationIssues.getOrDefault(project,
+ new ConcurrentLinkedQueue<>())) {
+ if (issue.getSourcePath().equals(sourcePath) && hasKind(occurrences, issue.getKind())) {
+ issues.add(issue);
+ }
+ }
+ return issues;
+ }
+
+ private static SourceFile markConfigurationIssues(SourceFile source, List issues,
+ ExecutionContext ctx) {
+ Map kinds = new HashMap<>();
+ for (ConfigurationIssue issue : issues) {
+ kinds.put(issue.getTreeId(), issue.getKind());
+ }
+ if (source instanceof Properties.File) {
+ return (SourceFile) new PropertiesIsoVisitor() {
+ @Override
+ public Properties.File visitFile(Properties.File file, ExecutionContext p) {
+ // Comments are sibling content in a Properties.File (not entry-prefix metadata),
+ // so — mirroring org.openrewrite.properties.AddPropertyComment — each match is
+ // commented directly, leaving the flagged entry itself untouched (see
+ // markJavaConfigurationIssues for why that matters).
+ Properties.File withComments = file;
+ for (Properties.Content content : file.getContent()) {
+ ValueKind kind = kinds.get(content.getId());
+ if (kind != null) {
+ withComments = Comments.of(new Cursor(
+ new Cursor(getCursor().getParentOrThrow(), withComments), content))
+ .comment(" " + kind.invalidPropertyMessage);
+ }
+ }
+ return withComments;
+ }
+ }.visitNonNull(source, ctx);
+ }
+ return (SourceFile) new YamlIsoVisitor() {
+ @Override
+ public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionContext p) {
+ Yaml.Mapping.Entry e = super.visitMappingEntry(entry, p);
+ ValueKind kind = kinds.get(e.getValue().getId());
+ return kind == null ? e :
+ Comments.of(new Cursor(getCursor().getParentOrThrow(), e)).comment(" " + kind.invalidPropertyMessage);
+ }
+ }.visitNonNull(source, ctx);
+ }
+
+ private static boolean hasKind(List occurrences, ValueKind kind) {
+ return occurrences.stream().anyMatch(occurrence -> occurrence.getKind() == kind);
+ }
+}
diff --git a/src/main/java/org/openrewrite/java/spring/data/search/MongoValueRepresentationScanner.java b/src/main/java/org/openrewrite/java/spring/data/search/MongoValueRepresentationScanner.java
new file mode 100644
index 000000000..05e5df888
--- /dev/null
+++ b/src/main/java/org/openrewrite/java/spring/data/search/MongoValueRepresentationScanner.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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 org.openrewrite.java.spring.data.search;
+
+import org.jspecify.annotations.Nullable;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.SourceFile;
+import org.openrewrite.java.AnnotationMatcher;
+import org.openrewrite.java.JavaIsoVisitor;
+import org.openrewrite.java.MethodMatcher;
+import org.openrewrite.java.marker.JavaProject;
+import org.openrewrite.java.tree.Expression;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.JavaSourceFile;
+import org.openrewrite.java.tree.JavaType;
+import org.openrewrite.java.tree.TypeUtils;
+import org.openrewrite.properties.search.FindProperties;
+import org.openrewrite.properties.tree.Properties;
+import org.openrewrite.yaml.search.FindProperty;
+import org.openrewrite.yaml.tree.Yaml;
+
+import java.util.Set;
+
+import static org.openrewrite.java.spring.data.search.FindMissingMongoValueRepresentation.*;
+
+final class MongoValueRepresentationScanner {
+
+ private static final MethodMatcher UUID_REPRESENTATION =
+ new MethodMatcher("com.mongodb.MongoClientSettings$Builder uuidRepresentation(..)");
+ private static final MethodMatcher BIG_NUMBER_REPRESENTATION = new MethodMatcher(
+ "org.springframework.data.mongodb.core.convert.MongoCustomConversions$MongoConverterConfigurationAdapter bigDecimal(..)");
+ private static final AnnotationMatcher DOCUMENT =
+ new AnnotationMatcher("@org.springframework.data.mongodb.core.mapping.Document");
+ private static final AnnotationMatcher FIELD =
+ new AnnotationMatcher("@org.springframework.data.mongodb.core.mapping.Field");
+ private static final AnnotationMatcher MONGO_ID =
+ new AnnotationMatcher("@org.springframework.data.mongodb.core.mapping.MongoId");
+ private static final AnnotationMatcher DB_REF =
+ new AnnotationMatcher("@org.springframework.data.mongodb.core.mapping.DBRef");
+ private static final AnnotationMatcher DOCUMENT_REFERENCE =
+ new AnnotationMatcher("@org.springframework.data.mongodb.core.mapping.DocumentReference");
+ private static final AnnotationMatcher TRANSIENT =
+ new AnnotationMatcher("@org.springframework.data.annotation.Transient");
+ private static final AnnotationMatcher ID =
+ new AnnotationMatcher("@org.springframework.data.annotation.Id");
+
+ private MongoValueRepresentationScanner() {
+ }
+
+ static void scan(SourceFile source, JavaProject project, Accumulator acc, ExecutionContext ctx) {
+ if (source.getMarkers().findFirst(ProjectDiagnostic.class).isPresent()) {
+ acc.finalizedProjects.add(project);
+ }
+ if (source.getMarkers().findFirst(RowsRecorded.class).isPresent()) {
+ acc.rowsInsertedProjects.add(project);
+ }
+ if (source instanceof JavaSourceFile && SpringConfigFileSupport.isMainSource(source)) {
+ scanJavaConfiguration((JavaSourceFile) source, project, acc, ctx);
+ scanAffectedFields((JavaSourceFile) source, project, acc, ctx);
+ } else if (SpringConfigFileSupport.isMainSpringConfigurationFile(source)) {
+ scanPropertyConfiguration(source, project, acc);
+ acc.preferredConfigurationSource.merge(project, source.getSourcePath(),
+ SpringConfigFileSupport::preferredConfigurationSource);
+ }
+ }
+
+ private static void scanJavaConfiguration(JavaSourceFile source, JavaProject project, Accumulator acc,
+ ExecutionContext ctx) {
+ new JavaIsoVisitor() {
+ @Override
+ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext p) {
+ J.MethodInvocation m = super.visitMethodInvocation(method, p);
+ if (UUID_REPRESENTATION.matches(m)) {
+ recordJavaConfigurationAttempt(ValueKind.UUID, m, source, project, acc);
+ }
+ if (BIG_NUMBER_REPRESENTATION.matches(m)) {
+ recordJavaConfigurationAttempt(ValueKind.BIG_NUMBER, m, source, project, acc);
+ }
+ return m;
+ }
+ }.visit(source, ctx);
+ }
+
+ /**
+ * A matching call always counts as an attempt, whether its argument is valid — a faulty
+ * call (e.g. {@code uuidRepresentation(null)}) still means the project picked Java configuration
+ * for this kind, so it should be marked in place rather than suggested via a properties file.
+ */
+ private static void recordJavaConfigurationAttempt(ValueKind kind, J.MethodInvocation method,
+ JavaSourceFile source, JavaProject project, Accumulator acc) {
+ acc.markJavaAttempted(kind, project);
+ if (hasExplicitArgument(method)) {
+ acc.markConfigured(kind, project);
+ } else {
+ acc.addConfigurationIssue(project, new ConfigurationIssue(source.getSourcePath(), method.getId(), kind));
+ }
+ }
+
+ private static void scanAffectedFields(JavaSourceFile source, JavaProject project, Accumulator acc,
+ ExecutionContext ctx) {
+ new JavaIsoVisitor() {
+ @Override
+ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations declarations,
+ ExecutionContext p) {
+ J.VariableDeclarations d = super.visitVariableDeclarations(declarations, p);
+ J.ClassDeclaration owner = getCursor().firstEnclosing(J.ClassDeclaration.class);
+ if (owner == null || getCursor().firstEnclosing(J.MethodDeclaration.class) != null ||
+ !isExplicitlyMongoMapped(owner, d) || isIgnoredField(d)) {
+ return d;
+ }
+ String owningType = owner.getType() == null ? owner.getSimpleName() :
+ owner.getType().getFullyQualifiedName();
+ boolean uuid = containsPersistedType(d.getType(), UUID_TYPE);
+ boolean bigNumber = (containsPersistedType(d.getType(), BIG_DECIMAL_TYPE) ||
+ containsPersistedType(d.getType(), BIG_INTEGER_TYPE)) && !hasExplicitFieldTargetType(d);
+ for (J.VariableDeclarations.NamedVariable variable : d.getVariables()) {
+ if (uuid) {
+ acc.addOccurrence(project, new Occurrence(source.getSourcePath(), owner.getId(), owningType,
+ variable.getSimpleName(), ValueKind.UUID));
+ }
+ if (bigNumber && !isExcludedBigIntegerId(d, variable)) {
+ acc.addOccurrence(project, new Occurrence(source.getSourcePath(), owner.getId(), owningType,
+ variable.getSimpleName(), ValueKind.BIG_NUMBER));
+ }
+ }
+ return d;
+ }
+ }.visit(source, ctx);
+ }
+
+ private static void scanPropertyConfiguration(SourceFile source, JavaProject project, Accumulator acc) {
+ for (ValueKind kind : ValueKind.values()) {
+ if (source instanceof Properties.File) {
+ scanPropertiesProperty((Properties.File) source, project, acc, kind);
+ } else if (source instanceof Yaml.Documents) {
+ scanYamlProperty((Yaml.Documents) source, project, acc, kind);
+ }
+ }
+ }
+
+ private static void scanPropertiesProperty(Properties.File file, JavaProject project, Accumulator acc,
+ ValueKind kind) {
+ Set entries = FindProperties.find(file, kind.configurationProperty, true);
+ if (entries.isEmpty()) {
+ return;
+ }
+ acc.markPropertyAttempted(kind, project);
+ for (Properties.Entry entry : entries) {
+ if (kind.isConfiguredValue(entry.getValue().getText())) {
+ acc.markConfigured(kind, project);
+ } else {
+ acc.addConfigurationIssue(project,
+ new ConfigurationIssue(file.getSourcePath(), entry.getId(), kind));
+ }
+ }
+ }
+
+ private static void scanYamlProperty(Yaml.Documents documents, JavaProject project, Accumulator acc,
+ ValueKind kind) {
+ Set values = FindProperty.find(documents, kind.configurationProperty, true);
+ if (values.isEmpty()) {
+ return;
+ }
+ acc.markPropertyAttempted(kind, project);
+ for (Yaml.Block value : values) {
+ if (value instanceof Yaml.Scalar && kind.isConfiguredValue(((Yaml.Scalar) value).getValue())) {
+ acc.markConfigured(kind, project);
+ } else {
+ acc.addConfigurationIssue(project,
+ new ConfigurationIssue(documents.getSourcePath(), value.getId(), kind));
+ }
+ }
+ }
+
+ private static boolean hasExplicitArgument(J.MethodInvocation method) {
+ if (method.getArguments().isEmpty()) {
+ return false;
+ }
+ Expression argument = method.getArguments().get(0);
+ return !(argument instanceof J.Literal && ((J.Literal) argument).getValue() == null) &&
+ !isUnspecified(argument);
+ }
+
+ private static boolean isUnspecified(Expression expression) {
+ String name = simpleName(expression);
+ if (name == null && expression instanceof J.Literal && ((J.Literal) expression).getValue() != null) {
+ name = ((J.Literal) expression).getValue().toString();
+ }
+ return name != null && "unspecified".equalsIgnoreCase(name.trim());
+ }
+
+ private static boolean isExplicitlyMongoMapped(J.ClassDeclaration owner, J.VariableDeclarations declarations) {
+ return owner.getLeadingAnnotations().stream().anyMatch(DOCUMENT::matches) ||
+ declarations.getLeadingAnnotations().stream().anyMatch(annotation ->
+ FIELD.matches(annotation) || MONGO_ID.matches(annotation) || DB_REF.matches(annotation) ||
+ DOCUMENT_REFERENCE.matches(annotation));
+ }
+
+ private static boolean isIgnoredField(J.VariableDeclarations declarations) {
+ return declarations.hasModifier(J.Modifier.Type.Static) ||
+ declarations.hasModifier(J.Modifier.Type.Transient) ||
+ declarations.getLeadingAnnotations().stream().anyMatch(TRANSIENT::matches);
+ }
+
+ private static boolean isExcludedBigIntegerId(J.VariableDeclarations declarations,
+ J.VariableDeclarations.NamedVariable variable) {
+ return TypeUtils.isOfClassType(declarations.getType(), BIG_INTEGER_TYPE) && isBigIntegerId(declarations, variable);
+ }
+
+ private static boolean isBigIntegerId(J.VariableDeclarations declarations,
+ J.VariableDeclarations.NamedVariable variable) {
+ return declarations.getLeadingAnnotations().stream().anyMatch(annotation ->
+ ID.matches(annotation) || MONGO_ID.matches(annotation)) || "id".equals(variable.getSimpleName());
+ }
+
+ private static boolean hasExplicitFieldTargetType(J.VariableDeclarations declarations) {
+ for (J.Annotation annotation : declarations.getLeadingAnnotations()) {
+ if (!FIELD.matches(annotation) || annotation.getArguments() == null) {
+ continue;
+ }
+ for (Expression argument : annotation.getArguments()) {
+ if (argument instanceof J.Assignment) {
+ J.Assignment assignment = (J.Assignment) argument;
+ if (assignment.getVariable() instanceof J.Identifier &&
+ "targetType".equals(((J.Identifier) assignment.getVariable()).getSimpleName())) {
+ String targetType = simpleName(assignment.getAssignment());
+ return targetType != null && !"implicit".equalsIgnoreCase(targetType);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private static @Nullable String simpleName(Expression expression) {
+ if (expression instanceof J.Identifier) {
+ return ((J.Identifier) expression).getSimpleName();
+ }
+ return expression instanceof J.FieldAccess ? ((J.FieldAccess) expression).getSimpleName() : null;
+ }
+
+ private static boolean containsPersistedType(@Nullable JavaType type, String fullyQualifiedType) {
+ if (type == null) {
+ return false;
+ }
+ if (TypeUtils.isOfClassType(type, fullyQualifiedType)) {
+ return true;
+ }
+ if (type instanceof JavaType.Array) {
+ return containsPersistedType(((JavaType.Array) type).getElemType(), fullyQualifiedType);
+ }
+ if (type instanceof JavaType.Parameterized) {
+ JavaType.Parameterized p = (JavaType.Parameterized) type;
+ int first = TypeUtils.isAssignableTo("java.util.Map", p.getType()) ? 1 : 0;
+ for (int i = first; i < p.getTypeParameters().size(); i++) {
+ if (containsPersistedType(p.getTypeParameters().get(i), fullyQualifiedType)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/org/openrewrite/java/spring/data/search/SpringConfigFileSupport.java b/src/main/java/org/openrewrite/java/spring/data/search/SpringConfigFileSupport.java
new file mode 100644
index 000000000..69507d9bd
--- /dev/null
+++ b/src/main/java/org/openrewrite/java/spring/data/search/SpringConfigFileSupport.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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 org.openrewrite.java.spring.data.search;
+
+import org.jspecify.annotations.Nullable;
+import org.openrewrite.SourceFile;
+import org.openrewrite.java.marker.JavaProject;
+import org.openrewrite.java.spring.SpringConfigFile;
+import org.openrewrite.marker.SourceSet;
+import org.openrewrite.properties.tree.Properties;
+import org.openrewrite.yaml.tree.Yaml;
+
+import java.nio.file.Path;
+
+/**
+ * Locating and ranking a project's main-source Spring application configuration files.
+ * Shared by the scanner and diagnostics phases; independent of what those phases do with the result.
+ */
+final class SpringConfigFileSupport {
+
+ private SpringConfigFileSupport() {
+ }
+
+ static boolean isMainSource(SourceFile source) {
+ SourceSet sourceSet = source.getMarkers().findFirst(SourceSet.class).orElse(null);
+ if (sourceSet != null) {
+ return "main".equals(sourceSet.getName());
+ }
+ String path = source.getSourcePath().toString().replace('\\', '/');
+ return !path.startsWith("src/test/") && !path.contains("/src/test/");
+ }
+
+ static @Nullable JavaProject javaProject(SourceFile source) {
+ return source.getMarkers().findFirst(JavaProject.class).orElse(null);
+ }
+
+ static boolean isMainSpringConfigurationFile(SourceFile source) {
+ if (!(source instanceof Properties.File || source instanceof Yaml.Documents) || !isMainSource(source)) {
+ return false;
+ }
+ if (source.getMarkers().findFirst(SpringConfigFile.class).isPresent()) {
+ return true;
+ }
+ String filename = source.getSourcePath().getFileName().toString();
+ return isApplicationConfigurationFile(filename);
+ }
+
+ private static boolean isApplicationConfigurationFile(String filename) {
+ if ("application.properties".equals(filename) || "application.yml".equals(filename) ||
+ "application.yaml".equals(filename)) {
+ return true;
+ }
+ return filename.startsWith("application-") &&
+ (filename.endsWith(".properties") || filename.endsWith(".yml") || filename.endsWith(".yaml"));
+ }
+
+ static Path preferredConfigurationSource(Path left, Path right) {
+ int leftPriority = configurationSourcePriority(left);
+ int rightPriority = configurationSourcePriority(right);
+ if (leftPriority != rightPriority) {
+ return leftPriority < rightPriority ? left : right;
+ }
+ return left.toString().compareTo(right.toString()) <= 0 ? left : right;
+ }
+
+ private static int configurationSourcePriority(Path path) {
+ String filename = path.getFileName().toString();
+ if ("application.properties".equals(filename)) {
+ return 0;
+ }
+ if ("application.yml".equals(filename)) {
+ return 1;
+ }
+ return "application.yaml".equals(filename) ? 2 : 3;
+ }
+}
diff --git a/src/main/java/org/openrewrite/java/spring/data/search/package-info.java b/src/main/java/org/openrewrite/java/spring/data/search/package-info.java
new file mode 100644
index 000000000..f59a6ab9b
--- /dev/null
+++ b/src/main/java/org/openrewrite/java/spring/data/search/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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.
+ */
+@NullMarked @NonNullFields
+package org.openrewrite.java.spring.data.search;
+
+import org.jspecify.annotations.NullMarked;
+import org.openrewrite.internal.lang.NonNullFields;
diff --git a/src/main/java/org/openrewrite/java/spring/table/MongoValueRepresentationFields.java b/src/main/java/org/openrewrite/java/spring/table/MongoValueRepresentationFields.java
new file mode 100644
index 000000000..0f4fc3e06
--- /dev/null
+++ b/src/main/java/org/openrewrite/java/spring/table/MongoValueRepresentationFields.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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 org.openrewrite.java.spring.table;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreType;
+import lombok.Value;
+import org.openrewrite.Column;
+import org.openrewrite.DataTable;
+import org.openrewrite.Recipe;
+
+@JsonIgnoreType
+public class MongoValueRepresentationFields extends DataTable {
+
+ public MongoValueRepresentationFields(Recipe recipe) {
+ super(recipe, "MongoDB value representation fields",
+ "MongoDB-persisted fields that require an explicit value representation when migrating to Spring Data MongoDB 5.");
+ }
+
+ @Value
+ public static class Row {
+ @Column(displayName = "Source path",
+ description = "The path to the source file containing the affected field.")
+ String sourcePath;
+
+ @Column(displayName = "Owning type",
+ description = "The fully qualified name of the MongoDB-persisted type.")
+ String owningType;
+
+ @Column(displayName = "Field",
+ description = "The affected field name.")
+ String field;
+
+ @Column(displayName = "Value type",
+ description = "The value representation category that requires configuration.")
+ String valueType;
+
+ @Column(displayName = "Configuration property",
+ description = "The Spring configuration property that can supply the project-wide representation.")
+ String configurationProperty;
+ }
+}
diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv
index 800f31632..8aecb5f43 100644
--- a/src/main/resources/META-INF/rewrite/recipes.csv
+++ b/src/main/resources/META-INF/rewrite/recipes.csv
@@ -112,7 +112,7 @@ maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot2.Up
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_4,Migrate to Spring Boot 2.4,"Migrate applications to the latest Spring Boot 2.4 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs, and migrate configuration settings that have changes between versions. This recipe will also chain additional framework migrations (Spring Framework, Spring Data, etc) that are required as part of the migration to Spring Boot 2.4.",895,,,,Spring Boot 2.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 2](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_5,Upgrade to Spring Boot 2.5,Upgrade to Spring Boot 2.5 from any prior 2.x version.,1002,,,,Spring Boot 2.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 2](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_6,Migrate to Spring Boot 2.6,"Migrate applications to the latest Spring Boot 2.6 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs, and migrate configuration settings that have changes between versions. This recipe will also chain additional framework migrations (Spring Framework, Spring Data, etc) that are required as part of the migration to Spring Boot 2.6.",1119,,,,Spring Boot 2.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 2](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
-maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7,Migrate to Spring Boot 2.7,Upgrade to Spring Boot 2.7.,1159,,,,Spring Boot 2.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 2](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7,Migrate to Spring Boot 2.7,Upgrade to Spring Boot 2.7.,1160,,,,Spring Boot 2.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 2](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot2.search.CustomizingJooqDefaultConfiguration,In Spring Boot 2.5 a `DefaultConfigurationCustomizer` can now be used in favour of defining one or more `*Provider` beans,"To streamline the customization of jOOQ’s `DefaultConfiguration`, a bean that implements `DefaultConfigurationCustomizer` can now be defined. This customizer callback should be used in favour of defining one or more `*Provider` beans, the support for which has now been deprecated. See [Spring Boot 2.5 jOOQ customization](https://docs.spring.io/spring-boot/docs/2.5.x/reference/htmlsingle/#features.sql.jooq.customizing).",1,,,Search,Spring Boot 2.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 2](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot2.search.FindUpgradeRequirementsSpringBoot_2_5,Find patterns that require updating for Spring Boot 2.5,Looks for a series of patterns that have not yet had auto-remediation recipes developed for.,6,,,Search,Spring Boot 2.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 2](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.DependenciesDeclared"",""displayName"":""Dependencies declared"",""instanceName"":""Dependencies declared"",""description"":""Direct (first-order) dependencies declared by the project."",""columns"":[{""name"":""projectName"",""type"":""String"",""displayName"":""Project name"",""description"":""The name of the project that contains the dependency.""},{""name"":""sourceSet"",""type"":""String"",""displayName"":""Source set"",""description"":""The source set that contains the dependency.""},{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The resolved version.""},{""name"":""datedSnapshotVersion"",""type"":""String"",""displayName"":""Dated snapshot version"",""description"":""The resolved dated snapshot version or `null` if this dependency is not a snapshot.""},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Maven scope (e.g. `compile`, `test`) or Gradle configuration name (e.g. `implementation`, `testImplementation`). For Maven, defaults to `compile` when no scope is declared.""}]}]"
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot2.search.IntegrationSchedulerPoolRecipe,Integration scheduler pool size,"Spring Integration now reuses an available `TaskScheduler` rather than configuring its own. In a typical application setup relying on the auto-configuration, this means that Spring Integration uses the auto-configured task scheduler that has a pool size of 1. To restore Spring Integration’s default of 10 threads, use the `spring.task.scheduling.pool.size` property.",1,,,Search,Spring Boot 2.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 2](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
@@ -147,7 +147,7 @@ maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.Re
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.ReplaceRestTemplateBuilderMethods,Replace deprecated setters in `RestTemplateBuilder`,"Replaces `setConnectTimeout`, `setReadTimeout`, and `setSslBundle` method invocations with `connectTimeout`, `readTimeout`, and `sslBundle` respectively.",5,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.ReplaceRestTemplateBuilderRequestFactoryMethod,Replace `RestTemplateBuilder.requestFactory(Function)` with `requestFactoryBuilder`,"`RestTemplateBuilder.requestFactory(java.util.function.Function)` was deprecated since Spring Boot 3.4, in favor of `requestFactoryBuilder(ClientHttpRequestFactoryBuilder)`.",1,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.ReplaceStringLiteralsWithConstants,Replace String literals with Spring constants,Replace String literals with Spring constants where applicable.,97,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
-maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.SpringBoot33BestPractices,Spring Boot 3.3 best practices,Applies best practices to Spring Boot 3 applications.,3270,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.SpringBoot33BestPractices,Spring Boot 3.3 best practices,Applies best practices to Spring Boot 3 applications.,3272,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.SpringBoot3BestPracticesOnly,Spring Boot 3.3 best practices (only),"Applies best practices to Spring Boot 3 applications, without chaining in upgrades to Spring Boot.",109,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.SpringBootProperties_3_0,Migrate Spring Boot properties to 3.0,Migrate properties found in `application.properties` and `application.yml`.,284,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.SpringBootProperties_3_1,Migrate Spring Boot properties to 3.1,Migrate properties found in `application.properties` and `application.yml`.,7,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
@@ -167,12 +167,12 @@ maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.Up
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.UpgradeMyBatisToSpringBoot_2_7,Upgrade MyBatis to Spring Boot 2.7,Upgrade MyBatis Spring modules to a version corresponding to Spring Boot 2.7.,16,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.UpgradeMyBatisToSpringBoot_3_0,Upgrade MyBatis to Spring Boot 3.0,Upgrade MyBatis Spring modules to a version corresponding to Spring Boot 3.0.,18,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.UpgradeMyBatisToSpringBoot_3_2,Upgrade MyBatis to Spring Boot 3.2,Upgrade MyBatis Spring modules to a version corresponding to Spring Boot 3.2.,20,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
-maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0,Migrate to Spring Boot 3.0,"Migrate applications to the latest Spring Boot 3.0 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs, and migrate configuration settings that have changes between versions. This recipe will also chain additional framework migrations (Spring Framework, Spring Data, etc) that are required as part of the migration to Spring Boot 2.7.",3087,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
-maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1,Migrate to Spring Boot 3.1,"Migrate applications to the latest Spring Boot 3.1 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs, and migrate configuration settings that have changes between versions. This recipe will also chain additional framework migrations (Spring Framework, Spring Data, etc) that are required as part of the migration to Spring Boot 3.0.",3146,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
-maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2,Migrate to Spring Boot 3.2,"Migrate applications to the latest Spring Boot 3.2 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs, and migrate configuration settings that have changes between versions. This recipe will also chain additional framework migrations (Spring Framework, Spring Data, etc) that are required as part of the migration to Spring Boot 3.1.",3222,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
-maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_3,Migrate to Spring Boot 3.3,"Migrate applications to the latest Spring Boot 3.3 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs, and migrate configuration settings that have changes between versions. This recipe will also chain additional framework migrations (Spring Framework, Spring Data, etc) that are required as part of the migration to Spring Boot 3.2.",3269,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
-maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_4,Migrate to Spring Boot 3.4,"Migrate applications to the latest Spring Boot 3.4 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs.",3407,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
-maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_5,Migrate to Spring Boot 3.5,"Migrate applications to the latest Spring Boot 3.5 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs.",3477,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0,Migrate to Spring Boot 3.0,"Migrate applications to the latest Spring Boot 3.0 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs, and migrate configuration settings that have changes between versions. This recipe will also chain additional framework migrations (Spring Framework, Spring Data, etc) that are required as part of the migration to Spring Boot 2.7.",3089,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1,Migrate to Spring Boot 3.1,"Migrate applications to the latest Spring Boot 3.1 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs, and migrate configuration settings that have changes between versions. This recipe will also chain additional framework migrations (Spring Framework, Spring Data, etc) that are required as part of the migration to Spring Boot 3.0.",3148,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2,Migrate to Spring Boot 3.2,"Migrate applications to the latest Spring Boot 3.2 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs, and migrate configuration settings that have changes between versions. This recipe will also chain additional framework migrations (Spring Framework, Spring Data, etc) that are required as part of the migration to Spring Boot 3.1.",3224,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_3,Migrate to Spring Boot 3.3,"Migrate applications to the latest Spring Boot 3.3 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs, and migrate configuration settings that have changes between versions. This recipe will also chain additional framework migrations (Spring Framework, Spring Data, etc) that are required as part of the migration to Spring Boot 3.2.",3271,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_4,Migrate to Spring Boot 3.4,"Migrate applications to the latest Spring Boot 3.4 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs.",3409,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_5,Migrate to Spring Boot 3.5,"Migrate applications to the latest Spring Boot 3.5 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs.",3479,,,,Spring Boot 3.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 3](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot4.AddAutoConfigureMockMvc,Add `@AutoConfigureMockMvc` if necessary,Adds `@AutoConfigureMockMvc` to `@SpringBootTest` classes that use `MockMvc` because Spring Boot 4 no longer auto-configures this bean.,1,,,,Spring Boot 4.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 4](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot4.AddAutoConfigureTestRestTemplate,Add `@AutoConfigureTestRestTemplate` if necessary,Adds `@AutoConfigureTestRestTemplate` to test classes annotated with `@SpringBootTest` that use `TestRestTemplate` since this bean is no longer auto-configured as described in the [Spring Boot 4 migration guide](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide#using-webclient-or-testresttemplate-and-springboottest).,1,,,,Spring Boot 4.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 4](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot4.AddAutoConfigureWebTestClient,Add `@AutoConfigureWebTestClient` if necessary,Adds `@AutoConfigureWebTestClient` to test classes annotated with `@SpringBootTest` that use `WebTestClient` since this bean is no longer auto-configured as described in the [Spring Boot 4 migration guide](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide#using-webclient-or-testresttemplate-and-springboottest).,1,,,,Spring Boot 4.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 4](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
@@ -192,7 +192,7 @@ maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot4.Re
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot4.SpringBootProperties_4_0,Migrate Spring Boot properties to 4.0,Migrate properties found in `application.properties` and `application.yml`.,155,,,,Spring Boot 4.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 4](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot4.SpringBootProperties_4_1,Migrate Spring Boot properties to 4.1,Migrate properties found in `application.properties` and `application.yml`.,6,,,,Spring Boot 4.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 4](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot4.UnwrapMockAndSpyBeanContainers,Unwrap `@MockBeans` and `@SpyBeans` container annotations,Replaces class-level `@MockBeans` and `@SpyBeans` container annotations with a single class-level `@MockBean` or `@SpyBean` annotation with a merged `types` attribute for compatibility with `@MockitoBean`.,1,,,,Spring Boot 4.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 4](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
-maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot4.UpgradeSpringBoot_4_0,Migrate to Spring Boot 4.0,"Migrate applications to the latest Spring Boot 4.0 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs.",4466,,,,Spring Boot 4.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 4](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.boot4.UpgradeSpringBoot_4_0,Migrate to Spring Boot 4.0,"Migrate applications to the latest Spring Boot 4.0 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs.",4468,,,,Spring Boot 4.x,Spring,Java,,,,Recipes for migrating to [Spring Boot 4](https://spring.io/projects/spring-boot).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.cloud2022.AddLoggingPatternLevelForSleuth,Add logging.pattern.level for traceId and spanId,"Add `logging.pattern.level` for traceId and spanId which was previously set by default, if not already set.",1,,,,Spring Cloud 2022,Spring,Java,,,,Recipes for migrating to Spring Cloud 2022.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.cloud2022.DependencyUpgrades,Upgrade dependencies to Spring Cloud 2022,Upgrade dependencies to Spring Cloud 2022 from prior 2021.x version.,11,,,,Spring Cloud 2022,Spring,Java,,,,Recipes for migrating to Spring Cloud 2022.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.cloud2022.MigrateCloudSleuthToMicrometerTracing,Migrate Spring Cloud Sleuth 3.1 to Micrometer Tracing 1.0,Spring Cloud Sleuth has been discontinued and only compatible with Spring Boot 2.x.,29,,,,Spring Cloud 2022,Spring,Java,,,,Recipes for migrating to Spring Cloud 2022.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
@@ -220,14 +220,15 @@ maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.data.Mig
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.data.MigrateQuerydslJpaRepository,Use `QuerydslPredicateExecutor`,"`QuerydslJpaRepository` was deprecated in Spring Data 2.1.",1,,,,Spring Data,Spring,Java,,,,Recipes for [Spring Data](https://spring.io/projects/spring-data) repositories and query methods.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.data.MigrateRepositoryRestConfigurerAdapter,Replace `RepositoryRestConfigurerAdapter` with `RepositoryRestConfigurer`,"Since 3.1, implement RepositoryRestConfigurer directly.",1,,,,Spring Data,Spring,Java,,,,Recipes for [Spring Data](https://spring.io/projects/spring-data) repositories and query methods.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.data.RefactorSimpleMongoDbFactory,Use `new SimpleMongoClientDbFactory(String)`,Replace usage of deprecated `new SimpleMongoDbFactory(new MongoClientURI(String))` with `new SimpleMongoClientDbFactory(String)`.,1,,,,Spring Data,Spring,Java,,,,Recipes for [Spring Data](https://spring.io/projects/spring-data) repositories and query methods.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
-maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.data.UpgradeSpringDataMongoDb_5_0,Migrate to Spring Data MongoDB 5.0,"Align explicitly versioned Spring Data MongoDB and supported MongoDB JVM driver dependencies with Spring Data MongoDB 5.0. Managed, versionless dependencies remain managed.",10,,,,Spring Data,Spring,Java,,,,Recipes for [Spring Data](https://spring.io/projects/spring-data) repositories and query methods.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.data.UpgradeSpringDataMongoDb_5_0,Migrate to Spring Data MongoDB 5.0,"Align explicitly versioned Spring Data MongoDB and supported MongoDB JVM driver dependencies with Spring Data MongoDB 5.0, and identify persisted values that require an explicit storage representation. Managed, versionless dependencies remain managed.",11,,,,Spring Data,Spring,Java,,,,Recipes for [Spring Data](https://spring.io/projects/spring-data) repositories and query methods.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.java.spring.table.MongoValueRepresentationFields"",""displayName"":""MongoDB value representation fields"",""instanceName"":""MongoDB value representation fields"",""description"":""MongoDB-persisted fields that require an explicit value representation when migrating to Spring Data MongoDB 5."",""columns"":[{""name"":""sourcePath"",""type"":""String"",""displayName"":""Source path"",""description"":""The path to the source file containing the affected field.""},{""name"":""owningType"",""type"":""String"",""displayName"":""Owning type"",""description"":""The fully qualified name of the MongoDB-persisted type.""},{""name"":""field"",""type"":""String"",""displayName"":""Field"",""description"":""The affected field name.""},{""name"":""valueType"",""type"":""String"",""displayName"":""Value type"",""description"":""The value representation category that requires configuration.""},{""name"":""configurationProperty"",""type"":""String"",""displayName"":""Configuration property"",""description"":""The Spring configuration property that can supply the project-wide representation.""}]}]"
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.data.UpgradeSpringData_2_3,Migrate to Spring Data 2.3,Migrate applications to the latest Spring Data 2.3 release.,9,,,,Spring Data,Spring,Java,,,,Recipes for [Spring Data](https://spring.io/projects/spring-data) repositories and query methods.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.data.UpgradeSpringData_2_5,Migrate to Spring Data JPA 2.5,Migrate applications to the latest Spring Data 2.5 release.,12,,,,Spring Data,Spring,Java,,,,Recipes for [Spring Data](https://spring.io/projects/spring-data) repositories and query methods.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.data.UpgradeSpringData_2_7,Migrate to Spring Data JPA 2.7,Migrate applications to the latest Spring Data JPA 2.7 release.,15,,,,Spring Data,Spring,Java,,,,Recipes for [Spring Data](https://spring.io/projects/spring-data) repositories and query methods.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.data.UpgradeSpringData_3_0,Migrate to Spring Data 3.0,"Migrate applications to Spring Data 3.0. Handles the PagingAndSortingRepository hierarchy change where it no longer extends CrudRepository, and chains prior deprecation fixes from Spring Data 2.7.",18,,,,Spring Data,Spring,Java,,,,Recipes for [Spring Data](https://spring.io/projects/spring-data) repositories and query methods.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.data.UpgradeSpringData_3_4,Migrate to Spring Data JPA 3.4,Migrate applications to the latest Spring Data JPA 3.4 release.,20,,,,Spring Data,Spring,Java,,,,Recipes for [Spring Data](https://spring.io/projects/spring-data) repositories and query methods.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
-maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.data.UpgradeSpringData_4_0,Migrate to Spring Data 4.0,Migrate applications to the Spring Data 2025.1 release train. Datastore-specific migration support is added incrementally.,31,,,,Spring Data,Spring,Java,,,,Recipes for [Spring Data](https://spring.io/projects/spring-data) repositories and query methods.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.data.UpgradeSpringData_4_0,Migrate to Spring Data 4.0,Migrate applications to the Spring Data 2025.1 release train. Datastore-specific migration support is added incrementally.,32,,,,Spring Data,Spring,Java,,,,Recipes for [Spring Data](https://spring.io/projects/spring-data) repositories and query methods.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.java.spring.table.MongoValueRepresentationFields"",""displayName"":""MongoDB value representation fields"",""instanceName"":""MongoDB value representation fields"",""description"":""MongoDB-persisted fields that require an explicit value representation when migrating to Spring Data MongoDB 5."",""columns"":[{""name"":""sourcePath"",""type"":""String"",""displayName"":""Source path"",""description"":""The path to the source file containing the affected field.""},{""name"":""owningType"",""type"":""String"",""displayName"":""Owning type"",""description"":""The fully qualified name of the MongoDB-persisted type.""},{""name"":""field"",""type"":""String"",""displayName"":""Field"",""description"":""The affected field name.""},{""name"":""valueType"",""type"":""String"",""displayName"":""Value type"",""description"":""The value representation category that requires configuration.""},{""name"":""configurationProperty"",""type"":""String"",""displayName"":""Configuration property"",""description"":""The Spring configuration property that can supply the project-wide representation.""}]}]"
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.data.UseTlsJdbcConnectionString,Use TLS for JDBC connection strings,"Increasingly, for compliance reasons (e.g. [NACHA](https://www.nacha.org/sites/default/files/2022-06/End_User_Briefing_Supplementing_Data_Security_UPDATED_FINAL.pdf)), JDBC connection strings should be TLS-enabled. This recipe will update the port and optionally add a connection attribute to indicate that the connection is TLS-enabled.",1,,,,Spring Data,Spring,Java,,,,Recipes for [Spring Data](https://spring.io/projects/spring-data) repositories and query methods.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,"[{""name"":""propertyKey"",""type"":""String"",""displayName"":""Property key"",""description"":""The Spring property key to perform updates against. If this value is specified, the specified property will be used for searching, otherwise a default of `spring.datasource.url` will be used instead."",""example"":""spring.datasource.url"",""required"":true},{""name"":""oldPort"",""type"":""Integer"",""displayName"":""Old port"",""description"":""The non-TLS enabled port number to replace with the TLS-enabled port. If this value is specified, no changes will be made to jdbc connection strings which do not contain this port number. "",""example"":""1234"",""required"":true},{""name"":""port"",""type"":""Integer"",""displayName"":""TLS port"",""description"":""The TLS-enabled port to use."",""example"":""1234"",""required"":true},{""name"":""attribute"",""type"":""String"",""displayName"":""Connection attribute"",""description"":""A connection attribute, if any, indicating to the JDBC provider that this is a TLS connection."",""example"":""sslConnection=true"",""required"":true}]",
+maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.data.search.FindMissingMongoValueRepresentation,Find missing MongoDB value representation configuration,"Find explicitly MongoDB-mapped UUID, BigInteger, and BigDecimal fields that require an explicit representation when migrating to Spring Data MongoDB 5. The recipe reports affected fields without choosing a storage representation.",1,,,Search,Spring Data,Spring,Java,,,,Recipes for [Spring Data](https://spring.io/projects/spring-data) repositories and query methods.,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.java.spring.table.MongoValueRepresentationFields"",""displayName"":""MongoDB value representation fields"",""instanceName"":""MongoDB value representation fields"",""description"":""MongoDB-persisted fields that require an explicit value representation when migrating to Spring Data MongoDB 5."",""columns"":[{""name"":""sourcePath"",""type"":""String"",""displayName"":""Source path"",""description"":""The path to the source file containing the affected field.""},{""name"":""owningType"",""type"":""String"",""displayName"":""Owning type"",""description"":""The fully qualified name of the MongoDB-persisted type.""},{""name"":""field"",""type"":""String"",""displayName"":""Field"",""description"":""The affected field name.""},{""name"":""valueType"",""type"":""String"",""displayName"":""Value type"",""description"":""The value representation category that requires configuration.""},{""name"":""configurationProperty"",""type"":""String"",""displayName"":""Configuration property"",""description"":""The Spring configuration property that can supply the project-wide representation.""}]}]"
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.doc.ApiInfoBuilderToInfo,Migrate `ApiInfoBuilder` to `Info`,Migrate SpringFox's `ApiInfoBuilder` to Swagger's `Info`.,4,,,,SpringDoc,Spring,Java,,,,Recipes for migrating from SpringFox to [springdoc-openapi](https://springdoc.org/).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.doc.MigrateDocketBeanToGroupedOpenApiBean,Migrate `Docket` to `GroupedOpenAPI`,"Migrate a `Docket` bean to a `GroupedOpenAPI` bean preserving group name, packages and paths. When possible the recipe will prefer property based configuration.",1,,,,SpringDoc,Spring,Java,,,,Recipes for migrating from SpringFox to [springdoc-openapi](https://springdoc.org/).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-spring,org.openrewrite.java.spring.doc.MigrateSpringFoxSecurityConfiguration,Migrate SpringFox `SecurityConfiguration` bean to Springdoc Swagger UI properties,"Replace `@Bean` methods that return `springfox.documentation.swagger.web.SecurityConfiguration` with the equivalent `springdoc.swagger-ui.*` configuration properties. Only literal builder arguments are migrated; beans with non-literal arguments or unsupported builder methods (`apiKey`, `apiKeyName`, `apiKeyVehicle`, `additionalQueryStringParams`) are left untouched for manual review. If no Spring application configuration file exists, the bean is left in place to avoid silently dropping configuration.",1,,,,SpringDoc,Spring,Java,,,,Recipes for migrating from SpringFox to [springdoc-openapi](https://springdoc.org/).,Recipes for upgrading and patching [Spring](https://spring.io/) applications.,Basic building blocks for transforming Java code.,,
diff --git a/src/main/resources/META-INF/rewrite/spring-data-4.yml b/src/main/resources/META-INF/rewrite/spring-data-4.yml
index 21dcb39f5..f95051583 100644
--- a/src/main/resources/META-INF/rewrite/spring-data-4.yml
+++ b/src/main/resources/META-INF/rewrite/spring-data-4.yml
@@ -33,7 +33,8 @@ name: org.openrewrite.java.spring.data.UpgradeSpringDataMongoDb_5_0
displayName: Migrate to Spring Data MongoDB 5.0
description: >-
Align explicitly versioned Spring Data MongoDB and supported MongoDB JVM driver
- dependencies with Spring Data MongoDB 5.0. Managed, versionless dependencies remain managed.
+ dependencies with Spring Data MongoDB 5.0, and identify persisted values that require
+ an explicit storage representation. Managed, versionless dependencies remain managed.
preconditions:
- org.openrewrite.java.dependencies.search.ModuleHasDependency:
groupIdPattern: org.springframework.data
@@ -85,3 +86,5 @@ recipeList:
artifactId: mongodb-driver-legacy
newVersion: 5.6.x
overrideManagedVersion: false
+ # Existing persisted data determines the correct representation, so report rather than choose one.
+ - org.openrewrite.java.spring.data.search.FindMissingMongoValueRepresentation
diff --git a/src/test/java/org/openrewrite/java/spring/data/UpgradeSpringDataMongoDb_5_0RepresentationTest.java b/src/test/java/org/openrewrite/java/spring/data/UpgradeSpringDataMongoDb_5_0RepresentationTest.java
new file mode 100644
index 000000000..f311f5393
--- /dev/null
+++ b/src/test/java/org/openrewrite/java/spring/data/UpgradeSpringDataMongoDb_5_0RepresentationTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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 org.openrewrite.java.spring.data;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.openrewrite.java.Assertions.java;
+import static org.openrewrite.java.Assertions.mavenProject;
+import static org.openrewrite.maven.Assertions.pomXml;
+import static org.openrewrite.properties.Assertions.properties;
+
+class UpgradeSpringDataMongoDb_5_0RepresentationTest implements RewriteTest {
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.recipeFromResources("org.openrewrite.java.spring.data.UpgradeSpringDataMongoDb_5_0")
+ .parser(JavaParser.fromJavaVersion().dependsOn(
+ """
+ package org.springframework.data.mongodb.core.mapping;
+ public @interface Document {}
+ """
+ ));
+ }
+
+ @Test
+ void runsRepresentationDiagnosticThroughComposite() {
+ rewriteRun(
+ spec -> spec.cycles(4).expectedCyclesThatMakeChanges(3),
+ mavenProject("app",
+ pomXml(
+ """
+
+ 4.0.0
+ com.example
+ example
+ 1.0.0
+
+
+ org.springframework.data
+ spring-data-mongodb
+ 4.5.13
+
+
+
+ """,
+ spec -> spec.after(actual -> assertThat(actual)
+ .containsPattern("spring-data-mongodb\\s*5\\.0\\.\\d+")
+ .actual())
+ ),
+ java(
+ """
+ package com.example;
+
+ import java.util.UUID;
+ import org.springframework.data.mongodb.core.mapping.Document;
+
+ @Document
+ class Account {
+ private UUID externalId;
+ }
+ """,
+ spec -> spec.path("src/main/java/com/example/Account.java")
+ ),
+ properties(
+ null,
+ spec -> spec
+ .path("src/main/resources/application.properties")
+ .after(actual -> assertThat(actual)
+ .contains("spring.mongodb.representation.uuid=UNSPECIFIED")
+ .actual())
+ )
+ )
+ );
+ }
+}
diff --git a/src/test/java/org/openrewrite/java/spring/data/search/FindMissingMongoValueRepresentationBoundariesTest.java b/src/test/java/org/openrewrite/java/spring/data/search/FindMissingMongoValueRepresentationBoundariesTest.java
new file mode 100644
index 000000000..8edc475ad
--- /dev/null
+++ b/src/test/java/org/openrewrite/java/spring/data/search/FindMissingMongoValueRepresentationBoundariesTest.java
@@ -0,0 +1,727 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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 org.openrewrite.java.spring.data.search;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.java.spring.table.MongoValueRepresentationFields;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.openrewrite.java.Assertions.java;
+import static org.openrewrite.java.Assertions.mavenProject;
+import static org.openrewrite.maven.Assertions.pomXml;
+import static org.openrewrite.properties.Assertions.properties;
+import static org.openrewrite.yaml.Assertions.yaml;
+
+class FindMissingMongoValueRepresentationBoundariesTest extends MongoValueRepresentationTestSupport {
+
+ @Test
+ void ignoresNonMongoAndTransientFields() {
+ rewriteRun(
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ """
+ package com.example;
+
+ import java.math.BigDecimal;
+ import java.util.UUID;
+ import org.springframework.data.annotation.Persistent;
+ import org.springframework.data.annotation.Transient;
+ import org.springframework.data.mongodb.core.mapping.Document;
+
+ class NotPersistent {
+ private UUID externalId;
+ private BigDecimal balance;
+ }
+
+ @Persistent
+ class OtherDataStoreEntity {
+ private UUID externalId;
+ private BigDecimal balance;
+ }
+
+ @Document
+ class Account {
+ private static UUID staticId;
+ private transient BigDecimal transientBalance;
+ @Transient
+ private UUID ignoredId;
+
+ void calculate() {
+ BigDecimal local = BigDecimal.ZERO;
+ }
+ }
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ void ignoresExplicitFieldRepresentationAndBigIntegerIds() {
+ rewriteRun(
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ """
+ package com.example;
+
+ import java.math.BigDecimal;
+ import java.math.BigInteger;
+ import org.springframework.data.annotation.Id;
+ import org.springframework.data.mongodb.core.mapping.Document;
+ import org.springframework.data.mongodb.core.mapping.Field;
+ import org.springframework.data.mongodb.core.mapping.FieldType;
+
+ @Document
+ class Account {
+ @Field(targetType = FieldType.DECIMAL128)
+ private BigDecimal balance;
+
+ @Id
+ private BigInteger identifier;
+
+ private BigInteger id;
+ }
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ void fieldLevelMongoAnnotationsQualifyWithoutDocumentAnnotation() {
+ rewriteRun(
+ spec -> spec.cycles(4).expectedCyclesThatMakeChanges(3)
+ .dataTable(MongoValueRepresentationFields.Row.class, rows ->
+ assertThat(rows)
+ .extracting(MongoValueRepresentationFields.Row::getField)
+ .containsExactlyInAnyOrder("mongoId", "reference", "linked")),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ """
+ package com.example;
+
+ import java.util.UUID;
+ import org.springframework.data.mongodb.core.mapping.DBRef;
+ import org.springframework.data.mongodb.core.mapping.DocumentReference;
+ import org.springframework.data.mongodb.core.mapping.MongoId;
+
+ class Account {
+ @MongoId
+ private UUID mongoId;
+
+ @DocumentReference
+ private UUID reference;
+
+ @DBRef
+ private UUID linked;
+ }
+ """,
+ spec -> spec.path("src/main/java/com/example/Account.java")
+ ),
+ properties(
+ null,
+ spec -> spec
+ .path("src/main/resources/application.properties")
+ .after(actual -> assertThat(actual)
+ .contains("spring.mongodb.representation.uuid=UNSPECIFIED")
+ .actual())
+ )
+ )
+ );
+ }
+
+ @Test
+ void mongoIdAnnotatedBigIntegerIsTreatedAsAnId() {
+ rewriteRun(
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ """
+ package com.example;
+
+ import java.math.BigInteger;
+ import org.springframework.data.mongodb.core.mapping.Document;
+ import org.springframework.data.mongodb.core.mapping.MongoId;
+
+ @Document
+ class Account {
+ @MongoId
+ private BigInteger identifier;
+ }
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ void reportsNestedValuesButNotMapKeys() {
+ rewriteRun(
+ spec -> spec.cycles(4).expectedCyclesThatMakeChanges(3)
+ .dataTable(MongoValueRepresentationFields.Row.class, rows ->
+ assertThat(rows)
+ .extracting(MongoValueRepresentationFields.Row::getField)
+ .containsExactlyInAnyOrder("externalIds", "balances")),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ """
+ package com.example;
+
+ import java.math.BigDecimal;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.UUID;
+ import org.springframework.data.mongodb.core.mapping.Document;
+
+ @Document
+ class Account {
+ private List externalIds;
+ private Map labelsByExternalId;
+ private Map balances;
+ }
+ """,
+ spec -> spec.path("src/main/java/com/example/Account.java")
+ ),
+ properties(
+ null,
+ spec -> spec
+ .path("src/main/resources/application.properties")
+ .after(actual -> assertThat(actual)
+ .contains("spring.mongodb.representation.uuid=UNSPECIFIED")
+ .contains("spring.data.mongodb.representation.big-decimal=UNSPECIFIED")
+ .actual())
+ )
+ )
+ );
+ }
+
+ @Test
+ void testJavaConfigurationDoesNotSuppressMainDiagnostics() {
+ rewriteRun(
+ spec -> spec.cycles(4).expectedCyclesThatMakeChanges(3),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ """
+ package com.example;
+
+ import java.util.UUID;
+ import org.springframework.data.mongodb.core.mapping.Document;
+
+ @Document
+ class Account {
+ private UUID externalId;
+ }
+ """,
+ spec -> spec.path("src/main/java/com/example/Account.java")
+ ),
+ java(
+ """
+ package com.example;
+
+ import com.mongodb.MongoClientSettings;
+ import org.bson.UuidRepresentation;
+
+ class TestMongoConfiguration {
+ void configure(MongoClientSettings.Builder builder) {
+ builder.uuidRepresentation(UuidRepresentation.STANDARD);
+ }
+ }
+ """,
+ spec -> spec.path("src/test/java/com/example/TestMongoConfiguration.java")
+ ),
+ properties(
+ null,
+ spec -> spec
+ .path("src/main/resources/application.properties")
+ .after(actual -> assertThat(actual)
+ .contains("spring.mongodb.representation.uuid=UNSPECIFIED")
+ .actual())
+ )
+ )
+ );
+ }
+
+ @Test
+ void nullJavaConfigurationDoesNotSuppressDiagnostics() {
+ rewriteRun(
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ accountWithUuidAndBigDecimal(),
+ spec -> spec.path("src/main/java/com/example/Account.java")
+ ),
+ java(
+ """
+ package com.example;
+
+ import com.mongodb.MongoClientSettings;
+ import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter;
+
+ class MongoConfiguration {
+ void configure(MongoClientSettings.Builder builder,
+ MongoConverterConfigurationAdapter adapter) {
+ builder.uuidRepresentation(null);
+ adapter.bigDecimal(null);
+ }
+ }
+ """,
+ spec -> spec.after(actual -> assertThat(actual)
+ .contains("// `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.")
+ .contains("builder.uuidRepresentation(null)")
+ .contains("// `spring.data.mongodb.representation.big-decimal` needs a concrete big-number representation matching the existing BSON data.")
+ .contains("adapter.bigDecimal(null)")
+ .doesNotContain("~~(")
+ .actual())
+ )
+ )
+ );
+ }
+
+ @Test
+ void explicitUnspecifiedJavaConfigurationDoesNotSuppressDiagnostics() {
+ rewriteRun(
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ accountWithUuidAndBigDecimal(),
+ spec -> spec.path("src/main/java/com/example/Account.java")
+ ),
+ java(
+ """
+ package com.example;
+
+ import com.mongodb.MongoClientSettings;
+ import org.bson.UuidRepresentation;
+ import org.springframework.data.mongodb.core.convert.MongoCustomConversions.BigDecimalRepresentation;
+ import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter;
+
+ class MongoConfiguration {
+ void configure(MongoClientSettings.Builder builder,
+ MongoConverterConfigurationAdapter adapter) {
+ builder.uuidRepresentation(UuidRepresentation.UNSPECIFIED);
+ adapter.bigDecimal(BigDecimalRepresentation.UNSPECIFIED);
+ }
+ }
+ """,
+ spec -> spec.after(actual -> assertThat(actual)
+ .contains("// `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.")
+ .contains("builder.uuidRepresentation(UuidRepresentation.UNSPECIFIED)")
+ .contains("// `spring.data.mongodb.representation.big-decimal` needs a concrete big-number representation matching the existing BSON data.")
+ .contains("adapter.bigDecimal(BigDecimalRepresentation.UNSPECIFIED)")
+ .doesNotContain("~~(")
+ .actual())
+ )
+ )
+ );
+ }
+
+ @Test
+ void testResourceConfigurationDoesNotSuppressMainDiagnostics() {
+ rewriteRun(
+ spec -> spec.cycles(4).expectedCyclesThatMakeChanges(3),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ accountWithUuidAndBigDecimal(),
+ spec -> spec.path("src/main/java/com/example/Account.java")
+ ),
+ properties(
+ """
+ spring.mongodb.representation.uuid=standard
+ spring.data.mongodb.representation.big-decimal=decimal128
+ """,
+ spec -> spec.path("src/test/resources/application.properties")
+ ),
+ properties(
+ null,
+ spec -> spec
+ .path("src/main/resources/application.properties")
+ .after(actual -> assertThat(actual)
+ .contains("spring.mongodb.representation.uuid=UNSPECIFIED")
+ .contains("spring.data.mongodb.representation.big-decimal=UNSPECIFIED")
+ .actual())
+ )
+ )
+ );
+ }
+
+ @Test
+ void unrelatedMainResourceIsNotUsedAsConfigurationTarget() {
+ rewriteRun(
+ spec -> spec.cycles(4).expectedCyclesThatMakeChanges(3),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ accountWithUuidAndBigDecimal(),
+ spec -> spec.path("src/main/java/com/example/Account.java")
+ ),
+ yaml(
+ """
+ logging:
+ level: INFO
+ """,
+ spec -> spec.path("src/main/resources/logback.yml")
+ ),
+ properties(
+ null,
+ spec -> spec
+ .path("src/main/resources/application.properties")
+ .after(actual -> assertThat(actual)
+ .contains("spring.mongodb.representation.uuid=UNSPECIFIED")
+ .contains("spring.data.mongodb.representation.big-decimal=UNSPECIFIED")
+ .actual())
+ )
+ )
+ );
+ }
+
+ @Test
+ void malformedYamlValuesMarkExistingEntries() {
+ rewriteRun(
+ spec -> spec.dataTable(MongoValueRepresentationFields.Row.class, rows -> assertThat(rows).hasSize(2)),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ yaml(
+ """
+ spring:
+ mongodb:
+ representation:
+ uuid:
+ unsupported: value
+ data:
+ mongodb:
+ representation:
+ big-decimal:
+ - decimal128
+ """,
+ """
+ spring:
+ mongodb:
+ representation:
+ # `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.
+ uuid:
+ unsupported: value
+ data:
+ mongodb:
+ representation:
+ # `spring.data.mongodb.representation.big-decimal` needs a concrete big-number representation matching the existing BSON data.
+ big-decimal:
+ - decimal128
+ """,
+ spec -> spec
+ .path("src/main/resources/application.yml")
+ .afterRecipe(file -> {
+ assertYamlEntryMarked(file, "uuid");
+ assertYamlEntryMarked(file, "big-decimal");
+ })
+ )
+ )
+ );
+ }
+
+ @Test
+ void reportsSharedDeclarationWhenOneBigIntegerIsNotAnId() {
+ rewriteRun(
+ spec -> spec.cycles(4).expectedCyclesThatMakeChanges(3)
+ .dataTable(MongoValueRepresentationFields.Row.class, rows ->
+ assertThat(rows)
+ .singleElement()
+ .extracting(MongoValueRepresentationFields.Row::getField)
+ .isEqualTo("sequence")),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ """
+ package com.example;
+
+ import java.math.BigInteger;
+ import org.springframework.data.mongodb.core.mapping.Document;
+
+ @Document
+ class Account {
+ private BigInteger id, sequence;
+ }
+ """,
+ spec -> spec.path("src/main/java/com/example/Account.java")
+ ),
+ properties(
+ null,
+ spec -> spec
+ .path("src/main/resources/application.properties")
+ .after(actual -> assertThat(actual)
+ .contains("spring.data.mongodb.representation.big-decimal=UNSPECIFIED")
+ .actual())
+ )
+ )
+ );
+ }
+
+ @Test
+ void isIdempotent() {
+ rewriteRun(
+ spec -> spec.cycles(4).expectedCyclesThatMakeChanges(3),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ """
+ package com.example;
+
+ import java.util.UUID;
+ import org.springframework.data.mongodb.core.mapping.Document;
+
+ @Document
+ class Account {
+ private UUID externalId;
+ }
+ """,
+ spec -> spec.path("src/main/java/com/example/Account.java")
+ ),
+ properties(
+ null,
+ spec -> spec
+ .path("src/main/resources/application.properties")
+ .after(actual -> assertThat(actual)
+ .contains("spring.mongodb.representation.uuid=UNSPECIFIED")
+ .actual())
+ )
+ )
+ );
+ }
+
+ @Test
+ void sourceWithoutJavaProjectMarkerIsIgnored() {
+ rewriteRun(
+ java(
+ """
+ package com.example;
+
+ import java.util.UUID;
+ import org.springframework.data.mongodb.core.mapping.Document;
+
+ @Document
+ class Account {
+ private UUID externalId;
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void faultyJavaConfigurationForOneKindCoexistsWithBaselineGenerationForAnother() {
+ rewriteRun(
+ spec -> spec.cycles(4).expectedCyclesThatMakeChanges(3),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ accountWithUuidAndBigDecimal(),
+ spec -> spec.path("src/main/java/com/example/Account.java")
+ ),
+ java(
+ """
+ package com.example;
+
+ import com.mongodb.MongoClientSettings;
+
+ class MongoConfiguration {
+ void configure(MongoClientSettings.Builder builder) {
+ builder.uuidRepresentation(null);
+ }
+ }
+ """,
+ spec -> spec.after(actual -> assertThat(actual)
+ .contains("// `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.")
+ .contains("builder.uuidRepresentation(null)")
+ .doesNotContain("~~(")
+ .actual())
+ ),
+ properties(
+ null,
+ spec -> spec
+ .path("src/main/resources/application.properties")
+ .after(actual -> assertThat(actual)
+ .contains("spring.data.mongodb.representation.big-decimal=UNSPECIFIED")
+ .doesNotContain("spring.mongodb.representation.uuid")
+ .actual())
+ )
+ )
+ );
+ }
+
+ @Test
+ void doesNotDuplicateAnAlreadyCommentedOutPlaceholderSuggestion() {
+ // Simulates the steady state after a prior cycle already created and commented the suggested
+ // property (an active, real UNSPECIFIED value — see
+ // MongoValueRepresentationDiagnostics.addUnspecifiedPropertySuggestion's javadoc). Scanning
+ // must recognize it as an existing attempt (not
+ // "unattempted"), otherwise propertiesToAdd would re-include the kind and produce a second,
+ // duplicate suggestion on top of the first.
+ rewriteRun(
+ spec -> spec.cycles(2).expectedCyclesThatMakeChanges(1),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ """
+ package com.example;
+
+ import java.util.UUID;
+ import org.springframework.data.mongodb.core.mapping.Document;
+
+ @Document
+ class Account {
+ private UUID externalId;
+ }
+ """,
+ spec -> spec.path("src/main/java/com/example/Account.java")
+ ),
+ properties(
+ """
+ # `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.
+ spring.mongodb.representation.uuid=UNSPECIFIED
+ """,
+ spec -> spec.path("src/main/resources/application.properties")
+ )
+ )
+ );
+ }
+
+ @Test
+ void doesNotDuplicateAnAlreadyCommentedOutPlaceholderSuggestionInYaml() {
+ // YAML equivalent of doesNotDuplicateAnAlreadyCommentedOutPlaceholderSuggestion: the
+ // suggestion is a real tree node (see mergeYamlSuggestion), so a prior run's suggestion is
+ // visible to FindProperty like any other entry — scanYamlProperty must recognize its
+ // UNSPECIFIED value as an ordinary invalid configuration rather than re-suggesting it.
+ rewriteRun(
+ spec -> spec.cycles(2).expectedCyclesThatMakeChanges(1),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ """
+ package com.example;
+
+ import java.util.UUID;
+ import org.springframework.data.mongodb.core.mapping.Document;
+
+ @Document
+ class Account {
+ private UUID externalId;
+ }
+ """,
+ spec -> spec.path("src/main/java/com/example/Account.java")
+ ),
+ yaml(
+ """
+ spring:
+ application:
+ name: example
+ mongodb:
+ representation:
+ # `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.
+ uuid: UNSPECIFIED
+ """,
+ spec -> spec.path("src/main/resources/application.yml")
+ )
+ )
+ );
+ }
+
+ @Test
+ void doesNotDuplicateAnAlreadyCommentedInvalidPropertyMessage() {
+ // Simulates a separate, later recipe invocation over a file a prior run already annotated:
+ // the still-invalid entry is visible to the scanner every time (unlike the missing-property
+ // placeholder, it never becomes a Properties.Comment), so this exercises Comments.of(...)'s
+ // own documented idempotency rather than any guard of ours. No SearchResult is applied to
+ // the entry itself (see MongoValueRepresentationDiagnostics), so re-running leaves the file
+ // byte-for-byte unchanged; PropertiesCommentService still returns a structurally-new (but
+ // textually identical) tree on the re-add, so one cycle is still needed to reach that state.
+ rewriteRun(
+ spec -> spec.cycles(2).expectedCyclesThatMakeChanges(1),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ properties(
+ """
+ # `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.
+ spring.mongodb.representation.uuid=unsupported
+ spring.data.mongodb.representation.big-decimal=decimal128
+ """,
+ spec -> spec.path("src/main/resources/application.properties")
+ )
+ )
+ );
+ }
+
+ @Test
+ void doesNotDuplicateAnAlreadyCommentedInvalidPropertyMessageInYaml() {
+ // YAML equivalent of doesNotDuplicateAnAlreadyCommentedInvalidPropertyMessage, confirming
+ // Comments.of(...)'s idempotency also holds for the YAML CommentService implementation
+ // (same structurally-new-but-textually-identical-tree behavior noted there).
+ rewriteRun(
+ spec -> spec.cycles(2).expectedCyclesThatMakeChanges(1),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ yaml(
+ """
+ spring:
+ mongodb:
+ representation:
+ # `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.
+ uuid: unsupported
+ data:
+ mongodb:
+ representation:
+ big-decimal: decimal128
+ """,
+ spec -> spec.path("src/main/resources/application.yml")
+ )
+ )
+ );
+ }
+
+ @Test
+ void doesNotDuplicateAnAlreadyCommentedInvalidJavaConfigurationMessage() {
+ // Java equivalent of doesNotDuplicateAnAlreadyCommentedInvalidPropertyMessage, confirming
+ // Comments.of(...)'s idempotency also holds for the Java CommentService implementation.
+ rewriteRun(
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ java(
+ """
+ package com.example;
+
+ import com.mongodb.MongoClientSettings;
+
+ class MongoConfiguration {
+ void configure(MongoClientSettings.Builder builder) {
+ // `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.
+ builder.uuidRepresentation(null);
+ }
+ }
+ """,
+ spec -> spec.path("src/main/java/com/example/MongoConfiguration.java")
+ )
+ )
+ );
+ }
+}
diff --git a/src/test/java/org/openrewrite/java/spring/data/search/FindMissingMongoValueRepresentationTest.java b/src/test/java/org/openrewrite/java/spring/data/search/FindMissingMongoValueRepresentationTest.java
new file mode 100644
index 000000000..edf29ac3a
--- /dev/null
+++ b/src/test/java/org/openrewrite/java/spring/data/search/FindMissingMongoValueRepresentationTest.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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 org.openrewrite.java.spring.data.search;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.java.spring.table.MongoValueRepresentationFields;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.openrewrite.java.Assertions.java;
+import static org.openrewrite.java.Assertions.mavenProject;
+import static org.openrewrite.maven.Assertions.pomXml;
+import static org.openrewrite.properties.Assertions.properties;
+import static org.openrewrite.yaml.Assertions.yaml;
+
+class FindMissingMongoValueRepresentationTest extends MongoValueRepresentationTestSupport {
+
+ @DocumentExample
+ @Test
+ void reportsOncePerProjectAndListsAllAffectedFields() {
+ rewriteRun(
+ spec -> spec.cycles(4).expectedCyclesThatMakeChanges(3)
+ .dataTable(MongoValueRepresentationFields.Row.class, rows ->
+ assertThat(rows)
+ .extracting(
+ MongoValueRepresentationFields.Row::getOwningType,
+ MongoValueRepresentationFields.Row::getField,
+ MongoValueRepresentationFields.Row::getValueType,
+ MongoValueRepresentationFields.Row::getConfigurationProperty)
+ .containsExactlyInAnyOrder(
+ tuple("com.example.Account", "externalId", "UUID", "spring.mongodb.representation.uuid"),
+ tuple("com.example.Account", "balance", "BigDecimal/BigInteger", "spring.data.mongodb.representation.big-decimal"),
+ tuple("com.example.Account", "sequence", "BigDecimal/BigInteger", "spring.data.mongodb.representation.big-decimal"))),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(
+ """
+ package com.example;
+
+ import java.math.BigDecimal;
+ import java.math.BigInteger;
+ import java.util.UUID;
+ import org.springframework.data.mongodb.core.mapping.Document;
+
+ @Document
+ class Account {
+ private UUID externalId;
+ private BigDecimal balance;
+ private BigInteger sequence;
+ }
+ """,
+ spec -> spec.path("src/main/java/com/example/Account.java")
+ ),
+ properties(
+ null,
+ spec -> spec
+ .path("src/main/resources/application.properties")
+ .after(actual -> assertThat(actual)
+ .contains("# `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.")
+ .contains("spring.mongodb.representation.uuid=UNSPECIFIED")
+ .contains("# `spring.data.mongodb.representation.big-decimal` needs a concrete big-number representation matching the existing BSON data.")
+ .contains("spring.data.mongodb.representation.big-decimal=UNSPECIFIED")
+ .actual())
+ )
+ )
+ );
+ }
+
+ @Test
+ void placesCommentedDiagnosticsInMainPropertiesFile() {
+ rewriteRun(
+ spec -> spec
+ .dataTable(MongoValueRepresentationFields.Row.class, rows -> assertThat(rows).hasSize(2)),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ properties(
+ """
+ spring.application.name=example
+ """,
+ spec -> spec
+ .path("src/main/resources/application.properties")
+ .after(actual -> assertThat(actual)
+ .contains("# `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.")
+ .contains("spring.mongodb.representation.uuid=UNSPECIFIED")
+ .contains("# `spring.data.mongodb.representation.big-decimal` needs a concrete big-number representation matching the existing BSON data.")
+ .contains("spring.data.mongodb.representation.big-decimal=UNSPECIFIED")
+ .doesNotContain("~~(")
+ .actual())
+ )
+ )
+ );
+ }
+
+ @Test
+ void placesCommentedDiagnosticsInMainYamlFile() {
+ rewriteRun(
+ spec -> spec
+ .dataTable(MongoValueRepresentationFields.Row.class, rows -> assertThat(rows).hasSize(2)),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ yaml(
+ """
+ spring:
+ application:
+ name: example
+ """,
+ """
+ spring:
+ application:
+ name: example
+ mongodb:
+ representation:
+ # `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.
+ uuid: UNSPECIFIED
+ data:
+ mongodb:
+ representation:
+ # `spring.data.mongodb.representation.big-decimal` needs a concrete big-number representation matching the existing BSON data.
+ big-decimal: UNSPECIFIED
+ """,
+ spec -> spec.path("src/main/resources/application.yml")
+ )
+ )
+ );
+ }
+
+ @Test
+ void prefersPropertiesFileOverYamlWhenBothArePresent() {
+ rewriteRun(
+ spec -> spec
+ .dataTable(MongoValueRepresentationFields.Row.class, rows -> assertThat(rows).hasSize(2)),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ properties(
+ """
+ spring.application.name=example
+ """,
+ spec -> spec
+ .path("src/main/resources/application.properties")
+ .after(actual -> assertThat(actual)
+ .contains("spring.mongodb.representation.uuid=UNSPECIFIED")
+ .contains("spring.data.mongodb.representation.big-decimal=UNSPECIFIED")
+ .actual())
+ ),
+ yaml(
+ """
+ spring:
+ application:
+ name: example
+ """,
+ spec -> spec.path("src/main/resources/application.yml")
+ )
+ )
+ );
+ }
+
+ @Test
+ void onlyProfileSpecificConfigurationFileStillReceivesSuggestions() {
+ rewriteRun(
+ spec -> spec
+ .dataTable(MongoValueRepresentationFields.Row.class, rows -> assertThat(rows).hasSize(2)),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ properties(
+ """
+ spring.application.name=example
+ """,
+ spec -> spec
+ .path("src/main/resources/application-prod.properties")
+ .after(actual -> assertThat(actual)
+ .contains("spring.mongodb.representation.uuid=UNSPECIFIED")
+ .contains("spring.data.mongodb.representation.big-decimal=UNSPECIFIED")
+ .actual())
+ )
+ )
+ );
+ }
+
+ @Test
+ void javaConfigurationSuppressesDiagnostics() {
+ rewriteRun(
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ java(
+ """
+ package com.example;
+
+ import com.mongodb.MongoClientSettings;
+ import org.bson.UuidRepresentation;
+ import org.springframework.data.mongodb.core.convert.MongoCustomConversions.BigDecimalRepresentation;
+ import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter;
+
+ class MongoConfiguration {
+ void configure(MongoClientSettings.Builder builder,
+ MongoConverterConfigurationAdapter adapter) {
+ builder.uuidRepresentation(UuidRepresentation.STANDARD);
+ adapter.bigDecimal(BigDecimalRepresentation.DECIMAL128);
+ }
+ }
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ void propertiesConfigurationSuppressesDiagnostics() {
+ rewriteRun(
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ properties(
+ """
+ spring.mongodb.representation.uuid=java-legacy
+ spring.data.mongodb.representation.big-decimal=string
+ """,
+ spec -> spec.path("src/main/resources/application.properties")
+ )
+ )
+ );
+ }
+
+ @Test
+ void yamlConfigurationSuppressesDiagnostics() {
+ rewriteRun(
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ yaml(
+ """
+ spring:
+ mongodb:
+ representation:
+ uuid: c-sharp-legacy
+ data:
+ mongodb:
+ representation:
+ big-decimal: decimal128
+ """,
+ spec -> spec.path("src/main/resources/application.yml")
+ )
+ )
+ );
+ }
+
+ @Test
+ void propertyPlaceholdersSuppressDiagnostics() {
+ rewriteRun(
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ properties(
+ """
+ spring.mongodb.representation.uuid=${MONGO_UUID_REPRESENTATION}
+ spring.data.mongodb.representation.big-decimal=${MONGO_BIG_DECIMAL_REPRESENTATION}
+ """,
+ spec -> spec.path("src/main/resources/application.properties")
+ )
+ )
+ );
+ }
+
+ @Test
+ void malformedPlaceholderDoesNotSuppressDiagnostics() {
+ rewriteRun(
+ spec -> spec.dataTable(MongoValueRepresentationFields.Row.class, rows -> assertThat(rows).hasSize(2)),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ properties(
+ """
+ spring.mongodb.representation.uuid=${MONGO_UUID_REPRESENTATION
+ spring.data.mongodb.representation.big-decimal=${MONGO_BIG_DECIMAL_REPRESENTATION
+ """,
+ """
+ # `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.
+ spring.mongodb.representation.uuid=${MONGO_UUID_REPRESENTATION
+ # `spring.data.mongodb.representation.big-decimal` needs a concrete big-number representation matching the existing BSON data.
+ spring.data.mongodb.representation.big-decimal=${MONGO_BIG_DECIMAL_REPRESENTATION
+ """,
+ spec -> spec
+ .path("src/main/resources/application.properties")
+ .afterRecipe(file -> {
+ assertPropertyMarked(file, "spring.mongodb.representation.uuid");
+ assertPropertyMarked(file, "spring.data.mongodb.representation.big-decimal");
+ })
+ )
+ )
+ );
+ }
+
+ @Test
+ void invalidProfileOverrideIsMarkedEvenWhenDefaultIsValid() {
+ rewriteRun(
+ spec -> spec.dataTable(MongoValueRepresentationFields.Row.class, rows ->
+ assertThat(rows)
+ .singleElement()
+ .extracting(MongoValueRepresentationFields.Row::getField)
+ .isEqualTo("externalId")),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ properties(
+ """
+ spring.mongodb.representation.uuid=standard
+ spring.data.mongodb.representation.big-decimal=decimal128
+ """,
+ spec -> spec.path("src/main/resources/application.properties")
+ ),
+ properties(
+ """
+ spring.mongodb.representation.uuid=unsupported
+ """,
+ """
+ # `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.
+ spring.mongodb.representation.uuid=unsupported
+ """,
+ spec -> spec.path("src/main/resources/application-test.properties")
+ )
+ )
+ );
+ }
+
+ @Test
+ void unspecifiedConfigurationMarksExistingProperties() {
+ rewriteRun(
+ spec -> spec.dataTable(MongoValueRepresentationFields.Row.class, rows -> assertThat(rows).hasSize(2)),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ properties(
+ """
+ spring.mongodb.representation.uuid=unspecified
+ spring.data.mongodb.representation.big-decimal=UNSPECIFIED
+ """,
+ """
+ # `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.
+ spring.mongodb.representation.uuid=unspecified
+ # `spring.data.mongodb.representation.big-decimal` needs a concrete big-number representation matching the existing BSON data.
+ spring.data.mongodb.representation.big-decimal=UNSPECIFIED
+ """,
+ spec -> spec
+ .path("src/main/resources/application.properties")
+ .afterRecipe(file -> {
+ assertPropertyMarked(file, "spring.mongodb.representation.uuid");
+ assertPropertyMarked(file, "spring.data.mongodb.representation.big-decimal");
+ })
+ )
+ )
+ );
+ }
+
+ @Test
+ void unsupportedAndBlankConfigurationMarksExistingProperties() {
+ rewriteRun(
+ spec -> spec.dataTable(MongoValueRepresentationFields.Row.class, rows -> assertThat(rows).hasSize(2)),
+ mavenProject("app",
+ pomXml(MINIMAL_POM),
+ java(accountWithUuidAndBigDecimal()),
+ properties(
+ """
+ spring.mongodb.representation.uuid=unsupported
+ spring.data.mongodb.representation.big-decimal=
+ """,
+ """
+ # `spring.mongodb.representation.uuid` needs a concrete UUID representation matching the existing BSON data.
+ spring.mongodb.representation.uuid=unsupported
+ # `spring.data.mongodb.representation.big-decimal` needs a concrete big-number representation matching the existing BSON data.
+ spring.data.mongodb.representation.big-decimal=
+ """,
+ spec -> spec
+ .path("src/main/resources/application.properties")
+ .afterRecipe(file -> {
+ assertPropertyMarked(file, "spring.mongodb.representation.uuid");
+ assertPropertyMarked(file, "spring.data.mongodb.representation.big-decimal");
+ })
+ )
+ )
+ );
+ }
+}
diff --git a/src/test/java/org/openrewrite/java/spring/data/search/MongoValueRepresentationTestSupport.java b/src/test/java/org/openrewrite/java/spring/data/search/MongoValueRepresentationTestSupport.java
new file mode 100644
index 000000000..c379983c5
--- /dev/null
+++ b/src/test/java/org/openrewrite/java/spring/data/search/MongoValueRepresentationTestSupport.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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 org.openrewrite.java.spring.data.search;
+
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.properties.PropertiesIsoVisitor;
+import org.openrewrite.properties.tree.Properties;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+import org.openrewrite.trait.Comments;
+import org.openrewrite.yaml.YamlIsoVisitor;
+import org.openrewrite.yaml.tree.Yaml;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+abstract class MongoValueRepresentationTestSupport implements RewriteTest {
+
+ protected static final String MINIMAL_POM =
+ """
+
+ 4.0.0
+ com.example
+ example
+ 1.0.0
+
+ """;
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.recipe(new FindMissingMongoValueRepresentation())
+ .parser(JavaParser.fromJavaVersion().dependsOn(
+ """
+ package org.springframework.data.mongodb.core.mapping;
+ public @interface Document {}
+ """,
+ """
+ package org.springframework.data.annotation;
+ public @interface Persistent {}
+ """,
+ """
+ package org.springframework.data.mongodb.core.mapping;
+ public enum FieldType { IMPLICIT, STRING, DECIMAL128, OBJECT_ID }
+ """,
+ """
+ package org.springframework.data.mongodb.core.mapping;
+ public @interface Field {
+ FieldType targetType() default FieldType.IMPLICIT;
+ }
+ """,
+ """
+ package org.springframework.data.mongodb.core.mapping;
+ public @interface MongoId {
+ FieldType value() default FieldType.IMPLICIT;
+ }
+ """,
+ """
+ package org.springframework.data.mongodb.core.mapping;
+ public @interface DBRef {}
+ """,
+ """
+ package org.springframework.data.mongodb.core.mapping;
+ public @interface DocumentReference {}
+ """,
+ """
+ package org.springframework.data.annotation;
+ public @interface Transient {}
+ """,
+ """
+ package org.springframework.data.annotation;
+ public @interface Id {}
+ """,
+ """
+ package org.bson;
+ public enum UuidRepresentation { UNSPECIFIED, STANDARD, JAVA_LEGACY }
+ """,
+ """
+ package com.mongodb;
+ public final class MongoClientSettings {
+ public static final class Builder {
+ public Builder uuidRepresentation(org.bson.UuidRepresentation representation) {
+ return this;
+ }
+ }
+ }
+ """,
+ """
+ package org.springframework.data.mongodb.core.convert;
+ public class MongoCustomConversions {
+ public enum BigDecimalRepresentation { UNSPECIFIED, STRING, DECIMAL128 }
+ public static class MongoConverterConfigurationAdapter {
+ public MongoConverterConfigurationAdapter bigDecimal(BigDecimalRepresentation representation) {
+ return this;
+ }
+ }
+ }
+ """
+ ));
+ }
+
+ protected static String accountWithUuidAndBigDecimal() {
+ return """
+ package com.example;
+
+ import java.math.BigDecimal;
+ import java.util.UUID;
+ import org.springframework.data.mongodb.core.mapping.Document;
+
+ @Document
+ class Account {
+ private UUID externalId;
+ private BigDecimal balance;
+ }
+ """;
+ }
+
+ protected static void assertPropertyMarked(Properties.File file, String property) {
+ AtomicBoolean found = new AtomicBoolean();
+ new PropertiesIsoVisitor() {
+ @Override
+ public Properties.Entry visitEntry(Properties.Entry entry, AtomicBoolean marked) {
+ Properties.Entry e = super.visitEntry(entry, marked);
+ if (property.equals(e.getKey()) &&
+ Comments.of(getCursor()).getComments().stream().anyMatch(comment -> comment.contains(property))) {
+ marked.set(true);
+ }
+ return e;
+ }
+ }.visit(file, found);
+ assertThat(found.get()).as("Expected property '%s' to carry a diagnostic comment", property).isTrue();
+ }
+
+ protected static void assertYamlEntryMarked(Yaml.Documents file, String key) {
+ AtomicBoolean found = new AtomicBoolean();
+ new YamlIsoVisitor() {
+ @Override
+ public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, AtomicBoolean marked) {
+ Yaml.Mapping.Entry e = super.visitMappingEntry(entry, marked);
+ if (e.getKey() instanceof Yaml.Scalar && key.equals(e.getKey().getValue()) &&
+ Comments.of(getCursor()).getComments().stream().anyMatch(comment -> comment.contains(key))) {
+ marked.set(true);
+ }
+ return e;
+ }
+ }.visit(file, found);
+ assertThat(found.get()).as("Expected YAML entry '%s' to carry a diagnostic comment", key).isTrue();
+ }
+}
diff --git a/src/test/java/org/openrewrite/java/spring/data/search/package-info.java b/src/test/java/org/openrewrite/java/spring/data/search/package-info.java
new file mode 100644
index 000000000..f59a6ab9b
--- /dev/null
+++ b/src/test/java/org/openrewrite/java/spring/data/search/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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.
+ */
+@NullMarked @NonNullFields
+package org.openrewrite.java.spring.data.search;
+
+import org.jspecify.annotations.NullMarked;
+import org.openrewrite.internal.lang.NonNullFields;