Bug report
- Creating @ConfigurationPropertiesproperty class that is used by a@Servicethat's annotated with@RefreshScopeusing JAVA 14 fails withcom.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: jdk.internal.loader.ClassLoaders$PlatformClassLoader["unnamedModule"]->java.lang.Module["classLoader"]
Describe the bug
- Create a @ConfigurationPropertiesclass
@Configuration// only loaded the bean when this was added
@ConfigurationProperties("gateway")
public class GatewayProperies {
  private String type;
  private String region;
...
}
- Create a Service with @RefreshScope
@Service
@RefreshScope
public class GatewayService {
  @Autowired
  private GatewayProperies properties;
...
...
- The serialization fails with the following:
com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: jdk.internal.loader.ClassLoaders$PlatformClassLoader["unnamedModule"]->java.lang.Module["classLoader"]->jdk.internal.loader.ClassLoaders$PlatformClassLoader["unnamedModule"]->java.lang.Module["classLoader"]->
- Fails when calling the controller endpoing /payments/props
@Controller
@RequestMapping("/v1")
public class GatewayController {
  @Autowired
  private GatewayService gatewayService;
  @RequestMapping(value = "/payments/props", method = RequestMethod.GET)
  public ResponseEntity<GatewayProperies> handle() {
    return new ResponseEntity<>(gatewayService.getProperties(), headers, HttpStatus.OK);
  }
}
- Trying to serialize the value fails with Jackson directly using an instance of ObjectMapperalso fails
    ObjectMapper mapper = new ObjectMapper();
    String jsonInString = "{}";
    try {
      jsonInString = mapper.writeValueAsString(gatewayService.getProperties());
      System.out.println(jsonInString);
    } catch (JsonProcessingException e) {
      e.printStackTrace();
    }
Version information
- SpringBoot version
    id 'org.springframework.boot' version '2.3.4.RELEASE'
- Java version
$ java -version
java version "14.0.2" 2020-07-14
Java(TM) SE Runtime Environment (build 14.0.2+12-46)
Java HotSpot(TM) 64-Bit Server VM (build 14.0.2+12-46, mixed mode, sharing)
- Jackson version
$ ./gradlew dependencies | grep -m 7 "jackson"
     |    +--- com.fasterxml.jackson.core:jackson-databind:2.11.2
     |    |    +--- com.fasterxml.jackson.core:jackson-annotations:2.11.2
     |    |    \--- com.fasterxml.jackson.core:jackson-core:2.11.2
     |    +--- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2
     |    |    +--- com.fasterxml.jackson.core:jackson-core:2.11.2
     |    |    \--- com.fasterxml.jackson.core:jackson-databind:2.11.2 (*)
     |    +--- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2
Expected behavior
- We should see the properties of the class
Additional context
- The full stacktrace of the calls....
com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: jdk.internal.loader.ClassLoaders$PlatformClassLoader["unnamedModule"]->java.lang.Module["classLoader"]->jdk.internal.loader.ClassLoaders$PlatformClassLoader["unnamedModule"]->java.lang.Module["classLoader"]->jdk.internal.loader.ClassLoaders$PlatformClassLoader["unnamedModule"]->java.lang.Module["classLoader"]-
...
...
>jdk.internal.loader.ClassLoaders$PlatformClassLoader["unnamedModule"]->java.lang.Module["classLoader"]->jdk.internal.loader.ClassLoaders$PlatformClassLoader["unnamedModule"]->java.lang.Module["classLoader"]->jdk.internal.loader.ClassLoaders$PlatformClassLoader["unnamedModule"]->java.lang.Module["classLoader"])
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770)
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:755)
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:755)
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
-->
Properties needed to be serialized
- The properties are rendered properly
- However, the instance of the bean ....$$EnhancerBySpringCGLIB$$8581eaa6#$$beanFactoryincludes property'$$beanFactory'is being added to the serialization...
property '$$beanFactory' (field "com....platform.payment.gateway.GatewayProperies$$EnhancerBySpringCGLIB$$8581eaa6#$$beanFactory, no static serializer)
Could this above ^^^^ be the problem?
Workaround
- The only way to get it properly working is to ignore the property "$$beanFactoryby adding@JsonIgnoreProperties
@Configuration// only loaded the bean when this was added
@ConfigurationProperties("gateway")
@JsonIgnoreProperties({"$$beanFactory"})
public class GatewayProperies {}
Comment From: wilkinsona
I believe the $$beanFactory is present due to the proxy that's created as a result of @Configuration. @Configuration is intended for classes that define beans using @Bean and doesn't really belong on a @ConfigurationProperties class. The recommended approach is to use @EnableConfigurationProperties or @ConfigurationPropertiesScan. Alternatively you could use @Component. 
If the above doesn't help and you would like us to take another look, please provide a complete and minimal sample that we can use to reproduce the problem. You can share it with us by zipping it up and attaching it to this issue or by pushing it to a separate repository on GitHub. We can then re-open the issue and take another look.
Comment From: marcellodesales
@wilkinsona You were right... Just replacing @Configuration with @Component worked!
Comment From: DheereshJoshi
This can be solved by specifying type in ObjectMapper.
mapper.writerFor(GatewayProperies.class).writeValueAsString(gatewayService.getProperties());
Comment From: gongshw
Inspired by @DheereshJoshi , I think you can use @JsonSerialize(as = XXXX.class) to let Jackson serialize the generated proxy as the raw class.
@Component
@RefreshScope
// This class is a @RefreshScope @Component,
// so it will be proxied by a cglib generated class at runtime,
// and can't be serialized simply by Jackson.
// To fix it, we have to let Jackson directly serialize the raw class
@JsonSerialize(as = SomeComponent.class)
class SomeComponent {
}