skip to Main Content

I have a Spring Boot backend application which has following part in the application.properties:

token.json_file_url=${TOKEN_JSON_FILE_URL:file:src/main/resources/localdev/user-claims.json}

${TOKEN_JSON_FILE_URL} env variable is not set, so the default file:src/main/… is in effect.

In a bundled library it’s used like this when a REST call is made:

@Value("${token.json_file_url}")
private String userClaimsTokenJsonFileUrl;
...
ObjectMapper objectMapper = new ObjectMapper(); //Jackson
Map<String, Object> map =
objectMapper.readValue(new URL(this.userClaimsTokenJsonFileUrl), new TypeReference<Map<String, Object>>() {});

Now executing the fat-jar generated by spring-boot-maven plugin runs perfectly fine in console via java -jar the-app.jar.

But copying the same jar into a docker container and firing a REST call against that yields a

java.io.FileNotFoundException: src/main/resources/localdev/user-claims.json.

How can that be?

To be complete, here’s the Dockerfile:

FROM amazoncorretto:17-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app/the-app.jar
WORKDIR /app
EXPOSE 8080
CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "the-app.jar"]

I also extracted the jar inside the container and the structure is:
BOOT-INF/classes/localdev/user-claims.json

It’s there. It just can’t be found running in a Docker container as opposed to running my jar locally.

Any ideas?

2

Answers


  1. Chosen as BEST ANSWER

    OK so actually the problem was much more stupid than I realised...

    I was executing the JAR from my root project directory. Like java -jar target/the-app.jar.

    What I didn't realize was that the path root/src/main/resources/localdev/... was available to the code running in the jar... It was not using the packed resources INSIDE the jar, but the external files...

    That's why the

    new URL(file:src/main/resources/...) 
    

    worked for the jar, while of course in the Docker container, this path wasn't there.

    Again, my mistake was that I thought the JAR used the resources packed inside the JAR, which it did not!

    Fixed it by copying the files also into the Docker container.


  2. Project Tree

    demo-springboot-json-app
    ├── Dockerfile
    ├── my-docker-data
    │   └── token-003.json
    ├── pom.xml
    ├── src
    │   └── main
    │       ├── java
    │       │   └── com
    │       │       └── example
    │       │           └── demo
    │       │               ├── DemoApplication.java
    │       │               └── HelloController.java
    │       └── resources
    │           ├── application.properties
    │           ├── logback.xml
    │           └── token-001.json
    └── token-002.json
    
    

    Test JSON

    token-001.json

    {
        "token": "AAAA",
        "expiresIn": 1100
    }
    

    token-002.json

    {
        "token": "BBBB",
        "expiresIn": 2200
    }
    

    token-003.json

    {
        "token": "CCCC",
        "expiresIn": 3300
    }
    

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>3.2.8</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>demo-springboot-json-app</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>demo-springboot-json-app</name>
        <description>demo-springboot-json-app project for Spring Boot</description>
    
        <properties>
            <maven.compiler.source>17</maven.compiler.source>
            <maven.compiler.target>17</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    application.properties

    spring.application.name=demo
    
    token.json_file_url=${TOKEN_JSON_FILE_URL:token-001.json}
    

    logback.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d [%thread] %-5level %-50logger{40} - %msg%n</pattern>
            </encoder>
        </appender>
         
        <appender name="RollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>app.log</file>
            <encoder>
                <pattern>%d [%thread] %-5level %-50logger{40} - %msg%n</pattern>
            </encoder>
             
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>app-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                <maxFileSize>1MB</maxFileSize>
                <maxHistory>30</maxHistory>
                <totalSizeCap>10MB</totalSizeCap>
                <cleanHistoryOnStart>true</cleanHistoryOnStart>
            </rollingPolicy>
        </appender>
         
        <root level="INFO">
            <appender-ref ref="Console" />
            <appender-ref ref="RollingFile" />
        </root>
    </configuration>
    

    DemoApplication.java

    package com.example.demo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    
    }
    

    HelloController.java

    package com.example.demo;
    
    import com.fasterxml.jackson.core.type.TypeReference;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Map;
    import java.net.URL;
    
    @RestController
    public class HelloController {
    
        private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
    
        @Value("${token.json_file_url}")
        private String userClaimsTokenJsonFileUrl;
    
    
        @GetMapping("/hello2/{name}")
        public String sayHello2(
                @PathVariable String name,
                @RequestParam(value = "greeting", defaultValue = "Hello") String greeting
        ) {
    
            logger.info(">>>> Token JSON File: {}", userClaimsTokenJsonFileUrl);
    
            ObjectMapper objectMapper = new ObjectMapper();
            Map<String, Object> map = null;
                    
            File file = new File(userClaimsTokenJsonFileUrl);
            if (file.exists()) {
                try {
                    map = objectMapper.readValue(file, new TypeReference<Map<String, Object>>() {
                    });
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            } else {
                ClassPathResource resource = new ClassPathResource(userClaimsTokenJsonFileUrl);
                try ( InputStream inputStream = resource.getInputStream()) {
                    {
                        if (inputStream == null) {
                            String errMsg = "File not found in classpath or file system: " + userClaimsTokenJsonFileUrl;
                            logger.error("{}", errMsg);
                            throw new IllegalArgumentException(errMsg);
                        }
                        map = objectMapper.readValue(inputStream, new TypeReference<Map<String, Object>>() {
                        });
                    }
                } catch (IOException ex) {
                    logger.error("{}", ex);
                    throw new RuntimeException(ex);
                }
            }
    
            if (map != null) {
                String token = (String) map.get("token");
                String expiresIn = String.valueOf(map.get("expiresIn"));
                logger.info(">>>> Token: " + token);
                logger.info(">>>> Expires In: " + expiresIn);
            } else {
                logger.error("CAN NOT READ MAP");
            }
    
    
            String msg = String.format("%s, %s!", greeting, name);
            logger.info(">>>> {} ", msg);
            return msg;
        }
        
    }
    

    Dockerfile

    # Build
    FROM maven:3.9.5-eclipse-temurin-17-alpine AS build
    WORKDIR /BUILDTMP
    COPY pom.xml .
    COPY src ./src
    RUN mvn clean package -DskipTests
    
    # Runtime
    FROM amazoncorretto:17-alpine
    ARG JAR_FILE=my-app.jar
    
    WORKDIR /app
    COPY --from=build /BUILDTMP/target/${JAR_FILE} ./app.jar
    
    EXPOSE 8080
    
    CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
    

    Build and Run

    Build

    mvn clean package
    

    Run

    Terminal-1

    java -jar target/demo-springboot-json-app-0.0.1-SNAPSHOT.jar
    

    Test-1

    Terminal-2

    curl http://localhost:8080/hello2/ABCD12345678
    

    check Terminal-1 console output

    >>>> Token JSON File: token-001.json
    >>>> Token: AAAA
    >>>> Expires In: 1100
    

    App read jar file’s token-001.json success.(Read file from classpath resource)

    In Terminal-1 , Ctrl + C , Stop App.

    Test-2 Set Environment

    Terminal-1

    export TOKEN_JSON_FILE_URL=token-002.json
    
    java -jar target/demo-springboot-json-app-0.0.1-SNAPSHOT.jar
    

    Terminal-2

    curl http://localhost:8080/hello2/ABCD12345678
    

    check Terminal-1 console output

    >>>> Token JSON File: token-002.json
    >>>> Token: BBBB
    >>>> Expires In: 2200
    

    App read token-002.json success.(Read file from Environment Config)

    In Terminal-1 , Ctrl + C , Stop App.

    Build Docker Container

    docker build 
      --build-arg JAR_FILE=demo-springboot-json-app-0.0.1-SNAPSHOT.jar 
      -t my-app-image .
    

    Docker Test-1

    Terminal-1

    docker run -it -p 8080:8080 my-app-image
    

    Terminal-2

    curl http://localhost:8080/hello2/ABCD12345678
    

    check Terminal-1 console output

    >>>> Token JSON File: token-001.json
    >>>> Token: AAAA
    >>>> Expires In: 1100
    

    App read jar file’s token-001.json success.(Read file from classpath resource)

    In Terminal-1 , Ctrl + C , Stop App.

    Docker Test-2 Set Environment

    Terminal-1

    docker run -it 
      -p 8080:8080 
      -v `pwd`/my-docker-data:/conf 
      -e TOKEN_JSON_FILE_URL=/conf/token-003.json 
      my-app-image
    

    Terminal-2

    curl http://localhost:8080/hello2/ABCD12345678
    

    check Terminal-1 console output

    >>>> Token JSON File: /conf/token-003.json
    >>>> Token: CCCC
    >>>> Expires In: 3300
    

    App read token-003.json success.(Read file from Environment Config)

    In Terminal-1 , Ctrl + C , Stop App.

    Another Solution About Read File

    I did not use the following writing method. The following writing method may be what you want:

        public InputStream loadFile(String path) throws Exception {
            URL url;
            
            //Read From Internet
            if (path.startsWith("http://") || path.startsWith("https://")) {
                url = new URL(path);
            } 
    
            //Read From Local File
            else if (path.startsWith("file:/")) {
                url = new URL(path);
            } 
    
            else {
            
            //Read From Classpath
            
                ClassLoader classLoader = getClass().getClassLoader();
                url = classLoader.getResource(path);
                if (url == null) {
                    throw new IllegalArgumentException("File not found in classpath: " + path);
                }
            }
            
            return url.openStream();
        }
    

    application.properties

    # Read From Classpath File
    token.json_file_url=${TOKEN_JSON_FILE_URL:token-001.json}
    
    # Read From Local File
    token.json_file_url=${TOKEN_JSON_FILE_URL:file:///path/to/your/token-002.json}
    
    # Read From Internet File
    token.json_file_url=${TOKEN_JSON_FILE_URL:http://hello.world.com/token-003.json}
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search