How to read a file from the resources folder in a Spring Boot application

Published by Alexander Braun on 14 Nov 2017 - tagged with Spring, Spring Boot, Java

This is a quick post that explains how to read a file from the resources folder (Classpath) within a Spring Boot command line application.

Introduction

For applications, I have implemented in the last couple of years, I quite often had the requirement to store additional lookup data within the resource folder of my Maven project. Interestingly, many solutions you will find on the internet will work well in an IDE like Eclipse, STS or IntelliJ, but not when you create a Spring Boot application. The main reason is that IDEs usually can access these resource files from the source or target folder. Though, some approaches don't work if the application is being packaged, for example when using mvn package from the spring-boot-maven-plugin.

To demonstrate a way to read files from the resource folder after packaged, I have created a small command line Spring Boot application that implements the following requirements.

We create a CountryService that returns a list of configured countries - country name and 2-letter ISO code. A country can be looked up by it's ISO code. The list of available countries will be stored as key-value pairs in a properties file. When the application starts up, we read the content of the properties file and store the result internally in a Map.

The code for this post is available in this GitHub repository.

Country properties file

The properties file I use as example stores four countries.

src/main/resources/data/countryCodes.properties

CA=Canada
DE=Germany
UK=United Kingdom
US=United States of America

Country service

Then we create the service interface and implementation in the package com.progressive.code.resource.service

CountryService interface

package com.progressive.code.resource.service;

import java.util.Map;

/**
 * Created by abraun on 14/11/2017.
 */
public interface CountryService {

    String getCountryByIsoCode(String isoCode);

    Map<String,String> getAllCountries();

}

CountryService implementation

Let's focus on the method readCountries() invoked by the constructor.

  • First, we create an instance of a Properties object
  • Then we create a ClassPathResource (from the Spring core.io package), using the location of the properties file within the resource folder
  • The ClassPathResource class allows us to read the properties based on an InputStream
  • And finally, we use forEach to iterate through the properties and store the ISO code as key and the country name as value within the HashMap
package com.progressive.code.resource.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * Created by abraun on 14/11/2017.
 */
@Service
public class CountryServiceImpl implements CountryService {

    private static final Logger LOGGER = LoggerFactory.getLogger(CountryServiceImpl.class);

    private static final String COUNTRY_DATA = "data/countryCodes.properties";

    private final Map<String, String> countries = new HashMap<>();

    public CountryServiceImpl() {
        readCountries();
        LOGGER.info("Loaded {} countries", countries.keySet().size());
    }

    @Override
    public String getCountryByIsoCode(String isoCode) {
        return countries.get(isoCode);
    }

    @Override
    public Map<string, string> getAllCountries() {
        return countries;
    }

    /**
     * This method reads the country codes properties file from the resource
     * folder (ClassPath) and stores the result in a HashMap.
     */
    private void readCountries() {
        try {

            //Instantiate a new Properties object
            Properties props = new Properties();

            //Create a ClassPathResource, based on the given location within the resource folder
            ClassPathResource res = new ClassPathResource(COUNTRY_DATA);

            //Load the properties using InputStream provided by the ClassPathResource
            props.load(res.getInputStream());

            //Map the properties to the HashMap
            props.stringPropertyNames().forEach(
                isoName -> countries.put(isoName, props.get(isoName).toString())
            );

        } catch (IOException e) {
            //For this example just log the exception
            LOGGER.error("Exception occurred while trying to read {}", COUNTRY_DATA, e);
        }
    }

}

Create a JUnit Test

Let's create a JUnit test to ensure that everything works as expected.

CountryServiceImplTest

package com.progressive.code.resource.service;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

/**
 * Created by abraun on 14/11/2017.
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class CountryServiceImplTest {

    @Autowired
    private CountryService countryService;

    @Test
    public void testGetCountryByIsoCode() {
        String countryName = countryService.getCountryByIsoCode("CA");
        assertEquals("Canada", countryName);
    }

    @Test
    public void testGetAllCountries() {
        Map countries = countryService.getAllCountries();
        assertNotNull(countries);
        assertEquals(4, countries.size());
        assertEquals("Canada", countries.get("CA"));
        assertEquals("Germany", countries.get("DE"));
    }
}

Get the Code on GitHhub

The code for this post is available in this GitHub repository.