diff --git a/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForConstructors.java b/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForConstructors.java
new file mode 100644
index 0000000000..2acba29fa2
--- /dev/null
+++ b/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForConstructors.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2025 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.migrate.lang.var;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import org.jspecify.annotations.Nullable;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Preconditions;
+import org.openrewrite.Recipe;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.java.JavaIsoVisitor;
+import org.openrewrite.java.search.UsesJavaVersion;
+import org.openrewrite.java.tree.*;
+
+import java.util.List;
+
+/**
+ * Replaces explicit type declarations with {@code var} keyword when the initializer
+ * is a constructor call with an exactly matching type.
+ *
+ *
This recipe is more conservative than {@link UseVarForObject} and
+ * {@link UseVarForGenericsConstructors}. It only transforms when the declared type
+ * exactly matches the constructor type, avoiding cases where the declared type is an
+ * interface or supertype.
+ */
+@Value
+@EqualsAndHashCode(callSuper = false)
+public class UseVarForConstructors extends Recipe {
+
+ String displayName = "Use `var` for constructor call assignments";
+
+ String description = "Replace explicit type declarations with `var` when the variable is initialized with a " +
+ "constructor call of exactly the same type. Does not transform when declared type " +
+ "differs from constructor type (e.g., interface vs implementation).";
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor() {
+ return Preconditions.check(new UsesJavaVersion<>(10), new JavaIsoVisitor() {
+ @Override
+ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations varDecl, ExecutionContext ctx) {
+ J.VariableDeclarations vd = super.visitVariableDeclarations(varDecl, ctx);
+
+ if (!DeclarationCheck.isVarApplicable(getCursor(), vd)) {
+ return vd;
+ }
+
+ Expression initializer = vd.getVariables().get(0).getInitializer();
+ if (initializer == null) {
+ return vd;
+ }
+ initializer = initializer.unwrap();
+
+ // Only transform constructor calls
+ if (!(initializer instanceof J.NewClass)) {
+ return vd;
+ }
+
+ // Declared type must exactly match constructor type, as to not have additional methods become available
+ if (!TypeUtils.isOfType(vd.getType(), initializer.getType())) {
+ return vd;
+ }
+
+ if (vd.getType() instanceof JavaType.FullyQualified) {
+ maybeRemoveImport((JavaType.FullyQualified) vd.getType());
+ }
+
+ return DeclarationCheck.transformToVar(vd, (J.NewClass nc) -> maybeTransferTypeArguments(vd, nc));
+ }
+
+ private J.NewClass maybeTransferTypeArguments(J.VariableDeclarations vd, J.NewClass initializer) {
+ TypeTree typeExpression = vd.getTypeExpression();
+ if (!(typeExpression instanceof J.ParameterizedType)) {
+ return initializer;
+ }
+ J.ParameterizedType paramType = (J.ParameterizedType) typeExpression;
+
+ List declaredTypeParams = paramType.getTypeParameters();
+ if (declaredTypeParams == null || declaredTypeParams.isEmpty()) {
+ return initializer;
+ }
+
+ TypeTree constructorClazz = initializer.getClazz();
+ if (!(constructorClazz instanceof J.ParameterizedType)) {
+ return initializer;
+ }
+ J.ParameterizedType constructorParamType = (J.ParameterizedType) constructorClazz;
+
+ List constructorTypeParams = constructorParamType.getTypeParameters();
+ boolean nullEmptyOrDiamondOperator = constructorTypeParams == null ||
+ constructorTypeParams.isEmpty() ||
+ constructorTypeParams.stream().allMatch(J.Empty.class::isInstance);
+ if (nullEmptyOrDiamondOperator) {
+ return initializer.withClazz(constructorParamType.withTypeParameters(declaredTypeParams));
+ }
+
+ return initializer;
+ }
+ });
+ }
+}
diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv
index 4bee6c6837..dc8751fc3c 100644
--- a/src/main/resources/META-INF/rewrite/recipes.csv
+++ b/src/main/resources/META-INF/rewrite/recipes.csv
@@ -336,6 +336,8 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.l
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.UseVar,Use local variable type inference,"Apply local variable type inference (`var`) for primitives and objects. These recipes can cause unused imports, be advised to run `org.openrewrite.java.RemoveUnusedImports afterwards.",5,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.FindVirtualThreadOpportunities,Find Virtual Thread opportunities,Find opportunities to convert existing code to use Virtual Threads.,10,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.java.table.MethodCalls"",""displayName"":""Method calls"",""description"":""The text of matching method invocations."",""columns"":[{""name"":""sourceFile"",""type"":""String"",""displayName"":""Source file"",""description"":""The source file that the method call occurred in.""},{""name"":""method"",""type"":""String"",""displayName"":""Method call"",""description"":""The text of the method call.""},{""name"":""className"",""type"":""String"",""displayName"":""Class name"",""description"":""The class name of the method call.""},{""name"":""methodName"",""type"":""String"",""displayName"":""Method name"",""description"":""The method name of the method call.""},{""name"":""argumentTypes"",""type"":""String"",""displayName"":""Argument types"",""description"":""The argument types of the method call.""}]}]"
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.FindNonVirtualExecutors,Find non-virtual `ExecutorService` creation,Find all places where static `java.util.concurrent.Executors` method creates a non-virtual `java.util.concurrent.ExecutorService`. This recipe can be used to search fro `ExecutorService` that can be replaced by Virtual Thread executor.,7,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.java.table.MethodCalls"",""displayName"":""Method calls"",""description"":""The text of matching method invocations."",""columns"":[{""name"":""sourceFile"",""type"":""String"",""displayName"":""Source file"",""description"":""The source file that the method call occurred in.""},{""name"":""method"",""type"":""String"",""displayName"":""Method call"",""description"":""The text of the method call.""},{""name"":""className"",""type"":""String"",""displayName"":""Class name"",""description"":""The class name of the method call.""},{""name"":""methodName"",""type"":""String"",""displayName"":""Method name"",""description"":""The method name of the method call.""},{""name"":""argumentTypes"",""type"":""String"",""displayName"":""Argument types"",""description"":""The argument types of the method call.""}]}]"
+maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.var.UseVarForConstructors,Use `var` for constructor call assignments,Replace explicit type declarations with `var` when the variable is initialized with a constructor call of exactly the same type. Does not transform when declared type differs from constructor type (e.g. interface vs implementation).,1,Var,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.var.UseVarForLiterals,Use `var` for literal assignments,Replace explicit type declarations with `var` when the variable is initialized with a String literal. Primitive literals are handled by `UseVarForPrimitive`.,1,Var,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.var.UseVarForGenericMethodInvocations,Apply `var` to generic method invocations,"Apply `var` to variables initialized by invocations of generic methods. This recipe ignores generic factory methods without parameters, because open rewrite cannot handle them correctly ATM.",1,Var,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.var.UseVarForGenericsConstructors,Apply `var` to Generic Constructors,Apply `var` to generics variables initialized by constructor calls.,1,Var,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.var.UseVarForObject,Use `var` for reference-typed variables,Try to apply local variable type inference `var` to variables containing Objects where possible. This recipe will not touch variable declarations with generics or initializers containing ternary operators.,1,Var,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
diff --git a/src/test/java/org/openrewrite/java/migrate/lang/var/UseVarForConstructorsTest.java b/src/test/java/org/openrewrite/java/migrate/lang/var/UseVarForConstructorsTest.java
new file mode 100644
index 0000000000..ee347b0bfd
--- /dev/null
+++ b/src/test/java/org/openrewrite/java/migrate/lang/var/UseVarForConstructorsTest.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2025 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.migrate.lang.var;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.java.Assertions.java;
+import static org.openrewrite.java.Assertions.javaVersion;
+
+class UseVarForConstructorsTest implements RewriteTest {
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.recipe(new UseVarForConstructors())
+ .allSources(s -> s.markers(javaVersion(17)));
+ }
+
+ @DocumentExample
+ @Test
+ void replacePatterns() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import java.io.ByteArrayInputStream;
+ import java.util.ArrayList;
+ import java.util.HashMap;
+
+ class Test {
+ void test() {
+ // Basic constructor
+ StringBuilder sb = new StringBuilder();
+
+ // Constructor with arguments
+ StringBuilder sbWithArg = new StringBuilder("initial");
+
+ // Final modifier
+ final StringBuilder finalSb = new StringBuilder();
+
+ // Generics with concrete types
+ ArrayList list = new ArrayList<>();
+
+ // Explicit type arguments on constructor (non-diamond)
+ ArrayList explicitList = new ArrayList();
+
+ // Nested generics with concrete types
+ HashMap> map = new HashMap<>();
+
+ // Type variable in generic
+ ArrayList typeVarList = new ArrayList<>();
+
+ // Multiple type variables
+ HashMap typeVarMap = new HashMap<>();
+
+ // Nested type variables
+ HashMap> nested = new HashMap<>();
+
+ // Inner class constructor
+ HashMap.SimpleEntry entry = new HashMap.SimpleEntry<>("key", 1);
+
+ // In lambda
+ Runnable r = () -> {
+ ArrayList lambdaList = new ArrayList<>();
+ };
+
+ // For-loop initializer
+ for (StringBuilder forSb = new StringBuilder(); forSb.length() < 10; forSb.append("x")) {
+ }
+
+ // Try-with-resources
+ try (ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0])) {
+ } catch (Exception e) {
+ }
+ }
+
+ // Instance initializer
+ {
+ StringBuilder initSb = new StringBuilder();
+ }
+
+ // Static initializer
+ static {
+ StringBuilder staticSb = new StringBuilder();
+ }
+ }
+ """,
+ """
+ import java.io.ByteArrayInputStream;
+ import java.util.ArrayList;
+ import java.util.HashMap;
+
+ class Test {
+ void test() {
+ // Basic constructor
+ var sb = new StringBuilder();
+
+ // Constructor with arguments
+ var sbWithArg = new StringBuilder("initial");
+
+ // Final modifier
+ final var finalSb = new StringBuilder();
+
+ // Generics with concrete types
+ var list = new ArrayList();
+
+ // Explicit type arguments on constructor (non-diamond)
+ var explicitList = new ArrayList();
+
+ // Nested generics with concrete types
+ var map = new HashMap>();
+
+ // Type variable in generic
+ var typeVarList = new ArrayList();
+
+ // Multiple type variables
+ var typeVarMap = new HashMap();
+
+ // Nested type variables
+ var nested = new HashMap>();
+
+ // Inner class constructor
+ var entry = new HashMap.SimpleEntry("key", 1);
+
+ // In lambda
+ Runnable r = () -> {
+ var lambdaList = new ArrayList();
+ };
+
+ // For-loop initializer
+ for (var forSb = new StringBuilder(); forSb.length() < 10; forSb.append("x")) {
+ }
+
+ // Try-with-resources
+ try (var bais = new ByteArrayInputStream(new byte[0])) {
+ } catch (Exception e) {
+ }
+ }
+
+ // Instance initializer
+ {
+ var initSb = new StringBuilder();
+ }
+
+ // Static initializer
+ static {
+ var staticSb = new StringBuilder();
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void doNotReplaceWhenJavaVersionBelow10() {
+ rewriteRun(
+ spec -> spec.allSources(s -> s.markers(javaVersion(9))),
+ //language=java
+ java(
+ """
+ class Test {
+ void test() {
+ StringBuilder sb = new StringBuilder();
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void doNotReplaceInvalidPatterns() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import java.util.ArrayList;
+ import java.util.List;
+
+ class Test {
+ // Field declarations
+ private final ArrayList privateField = new ArrayList<>();
+ protected final ArrayList protectedField = new ArrayList<>();
+ public final ArrayList publicField = new ArrayList<>();
+ final ArrayList packageField = new ArrayList<>();
+ ArrayList nonFinalField = new ArrayList<>();
+
+ void test() {
+ // Interface vs implementation
+ List list = new ArrayList<>();
+
+ // Supertype
+ Object obj = new StringBuilder();
+
+ // Already using var
+ var existing = new ArrayList<>();
+
+ // No initializer
+ ArrayList noInit;
+ noInit = new ArrayList<>();
+
+ // Not a constructor call
+ ArrayList fromFactory = getList();
+
+ // Multiple variables
+ String a = "a", b = "b";
+
+ // Null initializer
+ StringBuilder nullInit = null;
+
+ // Anonymous inner class
+ ArrayList anonList = new ArrayList() {};
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {}
+ };
+ }
+
+ ArrayList getList() {
+ return new ArrayList<>();
+ }
+ }
+ """
+ )
+ );
+ }
+}