A Convenient Way to Read Resources in Java

Alexei Klenin
The Startup
Published in
4 min readAug 14, 2020

I am working at Elasticsearch expert company and in my daily life, I see a lot of JSON: requests, responses, mappings, settings, etc. In 90% of cases, the functionality of software we make involves the manipulation of JSON. And it’s pretty common for us to have JSON files inside resources of Java code.

One day, after writing code to load and parse JSON for 100500th time I understood one thing: reading content of resources in Java is harder than it should be!

Just take a look. Let’s assume we have a resource com/adelean/junit/jupiter/resource.txt containing text:

“The quick brown fox jumps over the lazy dog.”

This is one of existing ways to load its content:

Looks scary, doesn’t it? Need to open and close multiple streams/readers, handle IOException, all just to get text content.

This Stackoverflow topic (https://stackoverflow.com/questions/15749192/how-do-i-load-a-file-from-resource-folder), and many others, explain how to load resources. You can give it a look, or better, to skip it and continue to read this article.

Luckily for us, there are great libraries like google Guava, that significantly simplify the task. Here is the same operation made with Guava:

Even if it’s simpler, it is still too complicated for a task that probably must be done with one line. And we still have to handle exceptions.

I always found it surprising why we must handle exceptions while loading java resources. While exception handling is justified for the files, because file can be absent, or doesn’t have right permissions, the situation is different for resources.

Resources lay on the classpath of Java application, not on the filesystem. You almost never will have IOException, unless you made a mistake in your code. Loading of resources is closer to class import than to reading of file from the filesystem. You don’t surround your imports with try-catch, do you ?

That's where @InjectResources library (https://github.com/hosuaby/inject-resources) comes to rescue. It offers a fluid Java DSL for loading and parsing of resources without boilerplate code. It also provides extensions for Spring, and testing frameworks (JUnit4/5). With @InjectResources, resource reading can become as simple as adding annotation on the class field:

@TextResource("com/adelean/junit/jupiter/resource.txt")
private String text;

Let’s take a look how it works!

The core of @InjectResources (https://hosuaby.github.io/inject-resources/0.1.0/asciidoc/#inject-resources-core) is a convenient and fluid Java DSL that takes away all boilerplate code like opening/closing of input streams and handling of I/O exceptions. First, you will need to add inject-resources-core to your project.

With Gradle:

compile group: 'com.adelean', name: 'inject-resources-core', version: '0.1.0'

Or with Maven:

<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>bintray</name>
<url>https://jcenter.bintray.com</url>
</repository>
</repositories>

<dependencies>
<dependency>
<groupId>com.adelean</groupId>
<artifactId>inject-resources-core</artifactId>
<version>0.1.0</version>
</dependency>
</dependencies>

The entry point of DSL is method InjectResources.resource. It’s recommended to import that method statically:

import static com.adelean.inject.resources.core.InjectResources.resource;

After we can use fluent syntax to get content of any resource:

var text = resource()
.withPath("/com/adelean/junit/jupiter", "resource.txt")
.text();

This syntax also offers various methods to open resources as binary InputStream or text Reader.

var schema = JavaPropsSchema
.emptySchema()
.withoutPathSeparator();

var reader = new JavaPropsMapper()
.readerFor(DbConnection.class)
.with(schema);

DbConnection dbConnection = resource()
.withPath("/com/adelean/junit/jupiter", "db.properties")
.asInputStream()
.parseChecked(reader::readValue);

Resources can also be read line by line in functional way:

var header = new AtomicReference<String>();
var lines = new ArrayList<String>();

resource()
.onClassLoaderOf(getClass())
.withPath("/com/adelean/junit/jupiter", "cities.csv")
.asLines()
.onFirstLine(header::set)
.forEachLine(lines::add);

Some method names finish with word Checked. Those methods can accept lambdas that may throw checked exceptions. They are very convenient to avoid exception handling while using parsers that may throw exceptions:

Instead of this:

var dbConnection = resource()
.withPath("db.properties")
.asInputStream()
.parse(inputStream -> {
try {
return reader.readValue(inputStream);
} catch (IOException parsingException) {
throw new RuntimeException(parsingException);
}
});

We can write this:

var dbConnection = resource()
.withPath("db.properties")
.asInputStream()
.parseChecked(reader::readValue);

inject-resources-core is a great alternative to low level libraries like Apache Commons or Google Guava. If you are interested, check the user guide for more information and examples: https://hosuaby.github.io/inject-resources/0.1.0/asciidoc/#inject-resources-core

But if you are using Spring, you even can get your resources parsed and injected directly into your beans. Next section will discuss integration of @InjectResources with Spring.

Spring has its own mechanism for resource loading. First we need to get reference to resource by one of three proposed methods. Using ResourceLoader:

@Autowired
private ResourceLoader resourceLoader;
// Later...var resource = resourceLoader
.getResource("classpath:com/adelean/junit/jupiter/resource.txt");

Using ResourceUtils:

var file = ResourceUtils
.getFile("classpath:com/adelean/junit/jupiter/resource.txt");

Or using Value annotation:

@Value("classpath:com/adelean/junit/jupiter/resource.txt")
private Resource resource;

After we got a reference to resource, we can read its content:

var textContent = Files.readString(resource.getFile().toPath());

Module inject-resources-spring (https://hosuaby.github.io/inject-resources/0.1.0/asciidoc/#inject-resources-spring) makes it possible to read content of resources with a single annotation. It also knows how to handle resources in various formats: binary, text, java properties, JSON and YAML.

First, add dependency to your project.

With Gradle:

compile group: 'com.adelean', name: 'inject-resources-spring', version: '0.1.0'

Or with Maven:

<dependency>
<groupId>com.adelean</groupId>
<artifactId>inject-resources-spring</artifactId>
<version>0.1.0</version>
</dependency>

then enable resource injection

@Configuration
@EnableResourceInjection
public class MyConfig {
}

After content of resources can be injected into components:

@Component
public class BeanWithTextResource {

@TextResource("/com/adelean/junit/jupiter/resource.txt")
private String text;
}

Until that moment, we only saw how to dial with text resources, but @InjectResources can do much more. We can load and parse automatically java properties.

@Component
public class BeanWithPropertiesResource {

@PropertiesResource("/com/adelean/junit/jupiter/db.properties")
private Properties dbProperties;
}

and JSON documents

@Component
public class BeanWithJsonResource {

@JsonResource("/com/adelean/junit/jupiter/sponge-bob.json")
private Map<String, Object> jsonAsMap;
}

and YAML documents

@Component
public class BeanWithYamlResource {

@YamlResource("/com/adelean/junit/jupiter/sponge-bob.yaml")
Person spongeBob;
}

Annotations JsonResource and YamlResource will automatically convert resource content to the right types. But in order to work, they need parser. JsonResource uses ObjectMapper from Jackson or Gson object as parser. Ensure that you have one in application context:

@Configuration
public class JacksonConfig {

@Bean
public ObjectMapper defaultObjectMapper() {
return new ObjectMapper();
}
}

YamlResource is using Snakeyaml, so bean of type Yaml must be present in context:

@Configuration
public class SnakeyamlConfig {

@Bean
public Yaml defaultYaml() {
return new Yaml();
}
}

This was an introduction into @InjectResource. This library is very simple but powerful. Don’t hesitate to take a look into documentation for more info and examples: https://hosuaby.github.io/inject-resources/0.1.0/asciidoc/#inject-resources-spring

In the following article “A convenient way to read a resource file in Java unit test” we will see how to inject resource content into JUnit tests.

--

--