How to configure Lombok extension to play well with VSCode

·

5 min read

26/7/22: Added VSCode Java July Update
15/1/22: Update to fix a typo

Update:

With the recent release of Language Support for Java(TM) by Red Hat 1.9, it now officially support lombok and Lombok Annotations Support for VS Code is no longer required.

Refer to VSCode Java July Update and VSCode Java Lombok Support for more information

Background

I was trying to figure out how to use an immutable object using Lombok @Value and @Jacksonized to play well with Jackson in spring-boot using VSCode

This is the sample code

@Value
@Builder
@Jacksonized
public class Flight {
    private String id;
    private String destination;
    private int flightTime;
    private int flightDuration;
}

@RestController
public class FlightController {
    @PostMapping("/flights")
    public Flight create(@RequestBody Flight flight) {
        return Flight.builder()
                .id(UUID.randomUUID().toString())
                .destination(flight.getDestination())
                .flightTime(flight.getFlightTime())
                .flightDuration(flight.getFlightDuration())
                .build();
    }
}

However, I kept getting the following error which I should not be getting

2022-01-06 01:13:01.899 DEBUG 14672 --- [nio-8080-exec-2] o.s.web.method.HandlerMethod             : Could not resolve parameter [0] in public com.trial.lombokjackson.Flight com.trial.lombokjackson.FlightController.create(com.trial.lombokjackson.Flight): Type definition error: [simple type, class com.trial.lombokjackson.Flight]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.trial.lombokjackson.Flight` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (PushbackInputStream); line: 2, column: 5]
2022-01-06 01:13:01.902 DEBUG 14672 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Failed to complete request: org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class com.trial.lombokjackson.Flight]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.trial.lombokjackson.Flight` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (PushbackInputStream); line: 2, column: 5]
2022-01-06 01:13:01.907 ERROR 14672 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class com.trial.lombokjackson.Flight]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.trial.lombokjackson.Flight` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (PushbackInputStream); line: 2, column: 5]] with root cause

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.trial.lombokjackson.Flight` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (PushbackInputStream); line: 2, column: 5]

// omitted the rest of the stack trace

@Jacksonized is supposed to play well with @Builder as mentioned in the docs

It automatically configures the generated builder class to be used by Jackson's deserialization

Attempt to fix

I tried to fall back to use @JacksonDeserialize instead of @Jacksonized to see if works

@Value
@Builder
@JsonDeserialize(builder = Flight.FlightBuilder.class)
public class Flight {
    private String id;
    private String destination;
    private int flightTime;
    private int flightDuration;

    @JsonPOJOBuilder(withPrefix = "")
    public static class FlightBuilder { }
}

And Voilà! it works perfectly, the error wasn't there anymore, and it deserializes as expected.

What happened?

So what happened? Based on the documentation for @Jacksonized, it is supposed to reduce the boilerplate code, and replace @JsonDeserialize, @JsonPOJOBuilder, ... and play well with @Builder

After trying for hours, I still wasn't able to figure it out and so I posted on stackoverflow to see if I am missing out on anything obvious, and the first comment triggered me to look into vscode-lombok-extension. So I went to vscode-lombok-github-issues to search for any similar issue (which I should have done way earlier), and I came across this, this and some others

TLDR; vscode-lombok extension comes with v1.18.12 instead of the latest stable version (v1.18.22) and @Jacksonized was only added from v1.18.14 onwards

Solution

The fix was pretty straightforward once we know that it was caused by an older version of Lombok. There are a couple of ways to resolve this:

#1 Replace Lombok jar

This solution is the recommended one and should be the easiest, as it only involves replacing the jar file and restarting of VSCode

  • Exit VSCode
  • Download the latest stable version from lombok
  • Navigate to %userprofile%\vscode\extensions\gabrielbb.vscode-lombok-1.0.1\server
    • delete the existing lombok.jar
    • copy the downloaded lombok.jar into the extension directory
  • Start VSCode
  • Press F1 or Ctrl + Shift + P and select Java: Clean Java Language Server Workspace
  • Restart VSCode when prompted

Note that if the extension will to be updated, it might point to the older lombok version again, so you would have to repeat the same process again

#2 Configure java.jdt.ls.vmargs

This involves tweaking the current java.jdt.ls.vmargs configuration to point to the latest lombok jar file in another directory

  • Open .vscode/settings.json in the project directory
  • Add "java.jdt.ls.vmargs": "-javaagent:/path/to/latest/lombok.jar
  • Restart VSCode when prompted

If it does not work, you may need to run this step

  • Press F1 or Ctrl + Shift + P and select Java: Clean Java Language Server Workspace
  • Restart VSCode when prompted

Note: java.jdt.ls.vmargs configuration in the vscode global setting will also be updated to include the new setting

// original
"java.jdt.ls.vmargs": "-javaagent:\"c:\\Users\\Joseph\\.vscode\\extensions\\gabrielbb.vscode-lombok-1.0.1\\server\\lombok.jar\"",

// after
"java.jdt.ls.vmargs": "-javaagent:./server/lombok.jar -javaagent:\"c:\\Users\\Joseph\\.vscode\\extensions\\gabrielbb.vscode-lombok-1.0.1\\server\\lombok.jar\"",

However, if you removed the configuration from .vscode/settings.json, the global settings will not be updated to remove the path. If the file no longer exist in the path, you will encounter error message The Language Support for Java server crashed 5 times in the last 3 minutes. The server will not be restarted.. In that case, launch global settings and remove the added path to return to the original configuration

#3 Run via build tool (gradle/maven/java)

Start the application using the following command

  • ./gradlew bootRun
  • mvn spring-boot:run
  • java -jar myapp.jar

This will bypass the VSCode and launch as the application where the lombok version should be the latest. At the time of writing, I am using spring-boot-2.6.2 which comes with lombok-1.18.22

Lesson learned

Always remember to check the lombok version installed for the IDE when encountered with weird behavior especially using newer feature(s). This applies to Eclipse as well since lombok jar is also installed and using an outdated lombok would have caused the same issue