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?
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
{
"token": "AAAA",
"expiresIn": 1100
}
{
"token": "BBBB",
"expiresIn": 2200
}
{
"token": "CCCC",
"expiresIn": 3300
}
<?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>
spring.application.name=demo
token.json_file_url=${TOKEN_JSON_FILE_URL:token-001.json}
<?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>
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);
}
}
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;
}
}
# 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"]
mvn clean package
Terminal-1
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-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.
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.
docker build \
--build-arg JAR_FILE=demo-springboot-json-app-0.0.1-SNAPSHOT.jar \
-t my-app-image .
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.
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.
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();
}
# 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}