Skip to content

UseMapOf rewrites the initializer of a HashMap-typed variable to Map.of(), leaving uncompilable code #1148

Description

@protocol7

What version of OpenRewrite are you using?

  • org.openrewrite:rewrite-core:8.85.4
  • org.openrewrite.recipe:rewrite-migrate-java:3.37.0

What is the smallest, simplest way to reproduce the problem?

UseMapOf rewrites the initializer of a variable to Map.of(..) but does not touch the variable's declared type. When the variable is declared with the concrete type java.util.HashMap (rather than the Map interface), the result no longer compiles: Map.of(..) returns an immutable java.util.Map, which is not assignable to a HashMap variable.

This only reproduces for a field — as a local variable the value tends to be inlined, and the declared type disappears.

@Test
void useMapOfLeavesIncompatibleHashMapDeclaredType() {
  rewriteRun(
      spec -> spec.recipe(new UseMapOf()),
      // language=java
      java(
          """
          package com.helloworld;

          import java.util.HashMap;

          class Main {
            private static final HashMap<String, String> VALUES =
                new HashMap<String, String>() {
                  {
                    put("key", "value");
                  }
                };
          }
          """,
          spec -> spec.markers(javaVersion(25))));
}

What did you see instead?

The recipe rewrites only the initializer and leaves the declared type as HashMap:

 import java.util.HashMap;
+import java.util.Map;

 class Main {
-  private static final HashMap<String, String> VALUES =
-      new HashMap<String, String>() {
-        {
-          put("key", "value");
-        }
-      };
+  private static final HashMap<String, String> VALUES = Map.of("key", "value");
 }

The rewritten source does not compile:

incompatible types: java.util.Map<java.lang.String,java.lang.String>
  cannot be converted to java.util.HashMap<java.lang.String,java.lang.String>

We hit this on a real test class during a batch run of the conventions recipes, and it had to be repaired by hand.

What did you expect to see?

Either:

  1. The recipe also widens the declared type to Map<String, String>, producing compilable code:

    private static final Map<String, String> VALUES = Map.of("key", "value");

    ...or

  2. The recipe skips the rewrite when the declared type is a concrete subtype (HashMap, LinkedHashMap, ...) that would not accept the Map.of(..) result.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions