micronaut-core: IntelliJ incremental build not working when modifying a class that is directly injected.

Expected Behavior

Ideally, IntelliJ with enabled “Build and run using” -> “IntelliJ IDEA” should be able to leverage its incremental build when Micronaut managed beans are modified.

image

Incremental build takes usually few milliseconds comparing to a full module rebuild which may take 30 second or 2 minutes or even longer. In such case, when a Micronaut based application has many beans, development becomes extremely painful especially if people write their code + tests in a TDD cycle with small increments.

Note that the full rebuild is triggered when a particular class that is being injected is changed. Such as

  • If an impl class is injected, then when that class is changed, a full rebuild is triggered.
  • if an interface is injected, then when that interface is changed, a full rebuild is triggered.

whereas

  • If an interface is injected, then modifying its impl doesn’t make IntelliJ to do a full rebuild.

Based on what I investigated, Micronaut 2 (2.2.1, 2.4.3, 2.5.13) and 3 (3.4.4) suffer from that behavior. However Micronaut 1 (I tried 1.3.7) was able to do incremental compilation.

In our project with 150 beans we suddenly hit the threshold. The full rebuild is taking more than 1 minute and because we like to inject beans without declaring interfaces for them (some folks are old enough to remember the J2EE days and are refusing to accept a workaround to “always” create an interface) we’re now completely desperate because of the slow turn around.

This is how incremental compilation in IntelliJ looks like: image

Actual Behaviour

When modifying a class file of a bean that is injected somewhere else, IntelliJ full rebuild is triggered.

This is what IntelliJ logs when MyService is injected into DemoController directly and is changed:

2022-05-25 15:52:55,193 [  32193]   FINE - #o.j.j.b.j.d.Mappings - Begin of Differentiate:
2022-05-25 15:52:55,193 [  32193]   FINE - #o.j.j.b.j.d.Mappings - Easy mode: false
2022-05-25 15:52:55,198 [  32198]   FINE - #c.i.u.i.FileChannelWithSizeTracking - Inst:/Users/stepan.vavra/Library/Caches/JetBrains/IntelliJIdea2022.1/compile-server/demo_7ab3cf90/mappings/sourceToClass.tab.values@33bd46cc,Thread[JPS thread pool,5,main],java.net.URLClassLoader@2626b418
2022-05-25 15:52:55,198 [  32198]   FINE - #c.i.u.i.FileChannelWithSizeTracking - read:/Users/stepan.vavra/Library/Caches/JetBrains/IntelliJIdea2022.1/compile-server/demo_7ab3cf90/mappings/sourceToClass.tab.values@33bd46cc,Thread[JPS thread pool,5,main],7412,168355
2022-05-25 15:52:55,208 [  32208]   FINE - #o.j.j.b.j.d.Mappings - Processing changed classes:
2022-05-25 15:52:55,209 [  32209]   FINE - #o.j.j.b.j.d.Mappings - Changed: com/example/MyService
2022-05-25 15:52:55,213 [  32213]   FINE - #o.j.j.b.j.d.Mappings - End of changed classes processing
2022-05-25 15:52:55,213 [  32213]   FINE - #o.j.j.b.j.d.Mappings - Checking dependent classes:
2022-05-25 15:52:55,216 [  32216]   FINE - #o.j.j.b.j.d.Mappings - Dependent class: com/example/$MyService$Definition$Reference
2022-05-25 15:52:55,228 [  32228]   FINE - #o.j.j.b.j.d.Mappings - Dependent class: com/example/$DemoController$Definition
2022-05-25 15:52:55,230 [  32230]   FINE - #o.j.j.b.j.d.Mappings - Turning non-incremental for the BuildTarget because dependent class is annotation-processor generated
2022-05-25 15:52:55,231 [  32231]   FINE - #o.j.j.b.j.d.Mappings - Dependent class: com/example/DemoController
2022-05-25 15:52:55,231 [  32231]   FINE - #o.j.j.b.j.d.Mappings - Dependent class: com/example/$MyService$Definition
2022-05-25 15:52:55,232 [  32232]   FINE - #o.j.j.i.Builder - Differentiate Results:
2022-05-25 15:52:55,232 [  32232]   FINE - #o.j.j.i.Builder -    Compiled Files:
2022-05-25 15:52:55,232 [  32232]   FINE - #o.j.j.i.Builder -       /Users/stepan.vavra/Repos/other/micronaut-test-slow-compilation/src/main/java/com/example/MyService.java
2022-05-25 15:52:55,233 [  32233]   FINE - #o.j.j.i.Builder -    Affected Files:
2022-05-25 15:52:55,233 [  32233]   FINE - #o.j.j.i.Builder - End Of Differentiate Results.
2022-05-25 15:52:55,233 [  32233]   INFO - #o.j.j.i.Builder - Non-incremental mode: Marking demo.main and direct dependants for recompilation

and a full rebuild is triggered.

This is how a full rebuild looks like in IntelliJ: image

Steps To Reproduce

Consider a simple demo app

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import jakarta.inject.Inject;

@Controller
public class DemoController {

    private final MyService myService;

    @Inject
    public DemoController(MyService myService) {
        this.myService = myService;
    }

    @Get("/message")
    String message() {
        return myService.hello("hello");
    }
}

with a service

import jakarta.inject.Singleton;

@Singleton
class MyService {

    public String hello(String input) {
        // comment and uncomment next 2 lines to get a full rebuild
        return "foo".equals(input) ? "hello3" : "HELLO";
//        return "foo".equals(input.toLowerCase()) ? "hello3" : "HELLO";
    }
}

When the MyService is changed in a way that its bytecode significantly changes (i.e., it’s not enough to change the string constants), that is to do for instance

return "foo".equals(input.toLowerCase()) ? "hello" : "HELLO";

Then when “Build” -> “Build module” is executed, IntelliJ marks the module for a full recompilation

Checking dependencies… [demo.main]
Marking demo.main and direct dependants for recompilation

Environment Information

As mentioned above, this problem happens with Micronaut 2.5.13 and 3.4.4 I tried IntelliJ 2022.1, 2021.3.3, 2021.3.2. JDK 17 and 11 on Mac.

Example Application

https://github.com/stepanv/micronaut-full-rebuild-sample-app

Version

3.4.4

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 17 (17 by maintainers)

Most upvoted comments

Thanks @graemerocher , that works perfectly!

Is the

Full recompilation is required because the generated resource 'META-INF/spring-configuration-metadata.json in CLASS_OUTPUT' must have exactly one originating element, but had 2. Analysis took 0.013 secs.

flaw of Micronaut or is it a Gradle thing? I have the reproducer ready so I’m ready to report it right away.