Search code examples
javaspringspring-bootyaml

Can't get Java see my custom YAML property in a Spring Boot app


Why doesn't it work?

gateway:
  servers:
    - local-server:
        url: https://localhost:${server.port}
        description: Api-Gateway-V2
  v1-prefix: /api/v1
package by.afinny.apigateway.constant;

import io.swagger.v3.oas.models.servers.Server;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public final class GatewayMeta {
    @Value("${gateway.servers}")
    public List<Server> servers;
    @Value("${gateway.v1-prefix}")
    public String v1Prefix;
    public List<Server> servers() {
        return servers;
    }
    public String v1Prefix() {
        return v1Prefix;
    }
}

import io.swagger.v3.oas.models.annotations.OpenAPI31;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

public class Server {
    private String url = null;
    private String description = null;
    private ServerVariables variables = null;
    private Map<String, Object> extensions = null;

    public Server() {
    }

// ...
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'gateway.servers' in value "${gateway.servers}"
    at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:180) ~[spring-core-6.0.12.jar:6.0.12]
    at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126) ~[spring-core-6.0.12.jar:6.0.12]
    at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239) ~[spring-core-6.0.12.jar:6.0.12]

I tried adding some "configuration processor" as a regular dependency as per this advice, but it didn't work (should I wrap it in some "processor" tags?)

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

I don't get any META-INF in my target after a build (should I?)

Spring Boot 3, Java 17

UPD

I tried this

package by.afinny.apigateway.constant;

import io.swagger.v3.oas.models.servers.Server;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@Setter
@ConfigurationProperties(value = "gateway")
public final class GatewayMeta {
    public List<Server> servers;
    public String v1Prefix;
    public List<Server> servers() {
        return servers;
    }
    public String v1Prefix() {
        return v1Prefix;
    }
}
# notice the camel case
gateway:
  servers:
    - local-server:
        url: https://localhost:${server.port}
        description: Api-Gateway-V2
  v1Prefix: /api/v1

I get an exception. It's relatively detailed, but I still don't understand what I need to do. Update how?

ERROR 19960 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Binding to target [Bindable@2a484710 type = java.util.List<io.swagger.v3.oas.models.servers.Server>, value = 'none', annotations = array<Annotation>[[empty]], bindMethod = [null]] failed:

    Property: gateway.servers[0].local-server.description
    Value: "Api-Gateway-V2"
    Origin: class path resource [application-prod.yml] - 53:22
    Reason: The elements [gateway.servers[0].local-server.description,gateway.servers[0].local-server.url] were left unbound.
    Property: gateway.servers[0].local-server.url
    Value: "https://localhost:${server.port}"
    Origin: class path resource [application-prod.yml] - 52:14
    Reason: The elements [gateway.servers[0].local-server.description,gateway.servers[0].local-server.url] were left unbound.

Action:

Update your application's configuration

I also included @EnableConfigurationProperties above my @SpringBootApplication class (I accidentally found it and decided to put it just in case)

I discovered it has to do with Server serialization: if I comment out everything related to servers, the prefix does get resolved. But why? Server does have getters and setters

Besides, I don't want my yaml to be forced to follow that camel case. I haven't found some equivalent of @JsonProperty for @ConfigurationProperties (like @ConfigurationProperty or something)


Solution

  • For managed complex list is suggested to use ConfigurationProperties. I think that is your yml structure wrong.

    I replicated here the case https://github.com/sbernardo/spring-issues-examples/tree/main/sof-questions-77646394

    Old yml:

    gateway:
      servers:
        - local-server:
            url: https://localhost:${server.port}
            description: Api-Gateway-V2
      v1Prefix: /api/v1
    

    New yml:

    gateway:
      servers:
        - local-server:
          url: https://localhost:${server.port}
          description: Api-Gateway-V2
        - local-server:
          url: https://localhost2:${server.port}
          description: Api-Gateway-V3
      v1Prefix: /api/v1
    

    As you can see there is a level that throw exception. Because in yaml you are define and array with - simbol with only local-server key and you are add another level on each object with fields url and description fields.

    I think key local-server is useless and you can remove it.

    Hope this will help :)