menu icon

A convenient way to load and parse resources in Java

Reading resources in Java is an old topic, and it looks like there is nothing more to say. But even in 2020 dealing with resources in Java still harder that it should be. Together, we will make a turn of existing solutions and discover a new convenient one.

A convenient way to load and parse resources in Java

Here at Adelean, we work with Elasticsearch and we are used to see a lot of JSONs: requests, responses, mappings, settings, etc. In 90% of cases, functionality of software we make involves manipulation of JSON. And it’s pretty common for us to have JSON files as 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 that 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:

String resourcePath = "com/adelean/junit/jupiter/resource.txt";
ClassLoader classLoader = getClass().getClassLoader();
              
StringBuilder textBuilder = new StringBuilder();
              
try (InputStream resourceAsStream = classLoader.getResourceAsStream(resourcePath);
     InputStreamReader streamReader = new InputStreamReader(resourceAsStream);
     BufferedReader bufferedReader = new BufferedReader(streamReader)) {
    int c = 0;
    while ((c = bufferedReader.read()) != -1) {
        textBuilder.append((char) c);
    }
} catch (IOException ioException) {
    // handle exception
}
              
String resourceContent = textBuilder.toString();

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, 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:

URL url = Resources.getResource("com/adelean/junit/jupiter/resource.txt");
try {
    String resourceContent = Resources.toString(url, StandardCharsets.UTF_8);
} catch (IOException ioException) {
    // handle exception
}

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 when manipulating 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 reading a 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:

repositories {
    jcenter()
}

dependencies {
    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);

Resource also can 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 a word “Checked”. Those methods can accept lambdas that may throw catched 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 components. 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 get content of resource 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 in your Spring project:

@Configuration
@EnableResourceInjection
public class MyConfig {
}

After content of resources can be injected in 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")
    private 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

Next time we will see how to read resources from JUnit tests.