Only if run through Eclipse: POST on Spring Boot Controller fails with HTTP Error 415

mfbieber :

The problem:

We have a working Spring Boot backend server (Java 11, Spring Boot 2.2.4.RELEASE) with a React frontend, which runs perfectly in a Docker container, if run through java -jar app.jar and if run through IntelliJ.

If it is run through Eclipse (running on Windows Server 2016) though, upon trying to send a POST with a JSON body, the following error is encountered:

2020-03-09 15:09:52.515  WARN 218960 --- [nio-8080-exec-1] .c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class xxx.xxx.xxx.xxx.xxx.xxx.xxx]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `xxx.xxx.xxx.xxx.xxx.xxx.xxx`: Argument #0 has no property name, is not Injectable: can not use as Creator [constructor for xxx.xxx.xxx.xxx.xxx.xxx.xxx, annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}]
2020-03-09 15:09:52.517  WARN 218960 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported]

This error ocurrs regardles of the headers set through the frontend code. So by setting Content-Type: application/json, the same error occurrs (see headers below).

Has anybody ever encountered something similar only happening in Eclipse?

I already checked the encoding (set everything to UTF-8 in Eclipse) and made sure that all environments use the same Java version (I am using AdoptOpenJDK 11.0.6.hs-adpt installed via SDKMan) in IntelliJ and Eclipse.

The compiled classes files look identical. The classpath when running the application contains the same jackson libraries (and looks very similar in total).

I believe it has something to do on how Eclipse runs the application. Does anyone have an idea on why this strange behavior is observed? Why only with Eclipse? And since we have some developers using Eclipse, how can we solve it?


More information about the request:

The request headers as seen through the browser:

POST /server/configure HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 510
Accept: application/json, text/plain, */*
Sec-Fetch-Dest: empty
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
Content-Type: application/json
Origin: http://localhost:3000
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Referer: http://localhost:3000/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,de-DE;q=0.8,de;q=0.7

And the response:

{"timestamp":"2020-03-09T14:00:27.285+0000","status":415,"error":"Unsupported Media Type","message":"Content type 'application/json;charset=UTF-8' not supported","path":"/server/configure"}

And the response headers:

HTTP/1.1 415
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://localhost:3000
Content-Type: application/json
Transfer-Encoding: chunked
Date: Mon, 09 Mar 2020 13:53:05 GMT
Keep-Alive: timeout=60
Connection: keep-alive

More information about the app:

The controller is as follows (simplified):

package xxx.xxx.xxx.xxx.xxx.xxx;

...
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
...

@Controller
@RequestMapping("/server")
@CrossOrigin(origins = "http://localhost:3000")
public class Controller {

    private someService SomeService;

    @Autowired
    public Controller(SomeService someService) {
        this.someService= someService;
    }

    @PostMapping(value = "/configure", consumes = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public String configureConnection(@RequestBody ConnectionConfiguration connectionConfiguration) throws MalformedURLException {
       String serverId = someService.configureConnection(connectionConfiguration);
       return serverId;
   }

}

The ConnectionConfiguration class is as follows (showing less properties here):

package xxx.xxx.xxx.xxx.xxx.xxx;

import com.fasterxml.jackson.annotation.JsonCreator;

public class ConnectionConfiguration {

    private String someProperty;

    @JsonCreator
    public ConnectionConfiguration(String someProperty) {
        this.someProperty= someProperty;
    }

    public String getSomeProperty() {
        return someProperty;
    }

}

Regarding the project structure, we have a root folder which contains a backend and a frontend folder. Each (root, backend and frontend) contains a build.gradle file, linked together through the build.gradle in the root folder. By doing this, we can make a production build that builds the frontend, copies the static HTML and JavaScript files to the backend's resources/static folder and then builds the Spring Boot jar. Then, in production mode, the frontend is delivered through Spring Boot's Tomcat.

For development, we typically start a Webpack dev server for the frontend through npm run start and the Spring Boot app from the Application.java class that contains a main method through the IDE:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
mfbieber :

After having equal classpaths, we checked if the built .class files were really the same - they were not (see below). We realized, that after setting the "Store information about method parameters (usable via reflection)" option in Eclipse's Preferences/Java/Compiler, everythinkg worked fine also when running the backend though Eclipse:

enter image description here

Knowing this, it makes sense why the deserialization was not working. Jackson relies on the parameter names in our constructor annotated with @JsonCreator to do so, and it gets the names via reflection.

See also:

https://github.com/FasterXML/jackson-modules-java8/tree/master/parameter-names

Why when a constructor is annotated with @JsonCreator, its arguments must be annotated with @JsonProperty?


Another solution, maybe safer because you don't have to edit the IDE's settings:

If we annotate the parameters with @JsonProperty("name"), it also works without the -parameters compile option:

package xxx.xxx.xxx.xxx.xxx.xxx;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class ConnectionConfiguration {

    private String someProperty;

    @JsonCreator
    public ConnectionConfiguration(@JsonProperty("someProperty") String someProperty) {
        this.someProperty= someProperty;
    }

    public String getSomeProperty() {
        return someProperty;
    }

}

The bytecode parts (without the @JsonProperty annotations):

Build with -parameters:

...
  // access flags 0x1
  // signature (Ljava/lang/String;)V
  // declaration: java.lang.String)
  public <init>(Ljava/lang/String;)V
    // parameter  someProperty
  @Lcom/fasterxml/jackson/annotation/JsonCreator;()
   L0
    LINENUMBER 17 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
...

Build without -parameters:

...
  // access flags 0x1
  // signature (Ljava/lang/String;)V
  // declaration: java.lang.String)
  public <init>(Ljava/lang/String;)V
  @Lcom/fasterxml/jackson/annotation/JsonCreator;()
   L0
    LINENUMBER 16 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
...

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=193720&siteId=1
Recommended