107

I am writing a drop-in replacement for a legacy application in Java. One of the requirements is that the ini files that the older application used have to be read as-is into the new Java Application. The format of this ini files is the common windows style, with header sections and key=value pairs, using # as the character for commenting.

I tried using the Properties class from Java, but of course that won't work if there is name clashes between different headers.

So the question is, what would be the easiest way to read in this INI file and access the keys?

Mario Ortegón
  • 17,860
  • 17
  • 67
  • 79

12 Answers12

127

The library I've used is ini4j. It is lightweight and parses the ini files with ease. Also it uses no esoteric dependencies to 10,000 other jar files, as one of the design goals was to use only the standard Java API

This is an example on how the library is used:

Ini ini = new Ini(new File(filename));
java.util.prefs.Preferences prefs = new IniPreferences(ini);
System.out.println("grumpy/homePage: " + prefs.node("grumpy").get("homePage", null));
NickSoft
  • 2,880
  • 5
  • 22
  • 43
Mario Ortegón
  • 17,860
  • 17
  • 67
  • 79
  • 2
    doesn't work, error says "IniFile cannot be resolved to a type" – Caballero May 03 '13 at 15:16
  • @Caballero yes it seems that `IniFile` class was taken out, try `Ini ini = new Ini(new File("/path/to/file"));` – Mehdi Karamosly Nov 09 '13 at 22:23
  • 2
    http://ini4j.sourceforge.net/tutorial/OneMinuteTutorial.java.html will probably stay up to date even if they change the class name again. – Lokathor Feb 09 '14 at 23:38
  • Is this thing even working anymore? Downloaded 0.5.4 source and it didn't even build, and it wasn't a missing dependency.. not worth the time to bother with it more. Also ini4j has a lot of other crap in there we don't need, Windoze registry editing... come on. #LinuxMasterRace ...But I guess if it works for you, knock yourself out. – User Aug 24 '16 at 02:32
  • For the INI file I wrote, I had to use the `Wini` class, as illustrated in the "One Minute" tutorial; the `prefs.node("something").get("val", null)` didn't work the way I expected. – Agi Hammerthief Mar 07 '18 at 15:53
  • Wasted my lot of time using this solution unsuccessfully. This is buggy. This project is last updated in 2015 and lot of tickets logged 3 years before are still open. For me nothing worked . I tried WINI, prefs.node and lot of other variations I tried but failed. Better to use well maintained code. – Vikas Piprade Sep 14 '20 at 13:20
66

As mentioned, ini4j can be used to achieve this. Let me show one other example.

If we have an INI file like this:

[header]
key = value

The following should display value to STDOUT:

Ini ini = new Ini(new File("/path/to/file"));
System.out.println(ini.get("header", "key"));

Check the tutorials for more examples.

Community
  • 1
  • 1
tshepang
  • 10,772
  • 21
  • 84
  • 127
  • 2
    Neat! I've always just been using BufferedReader and a bit of copy/paste String parsing code to not have to add yet another dependency to my applications (that can blow out of proportions when you start to add in third party APIs for even the simplest tasks). But I can't ignore this kind of simplicity. – Gimby Jan 08 '13 at 10:42
30

As simple as 80 lines:

package windows.prefs;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class IniFile {

   private Pattern  _section  = Pattern.compile( "\\s*\\[([^]]*)\\]\\s*" );
   private Pattern  _keyValue = Pattern.compile( "\\s*([^=]*)=(.*)" );
   private Map< String,
      Map< String,
         String >>  _entries  = new HashMap<>();

   public IniFile( String path ) throws IOException {
      load( path );
   }

   public void load( String path ) throws IOException {
      try( BufferedReader br = new BufferedReader( new FileReader( path ))) {
         String line;
         String section = null;
         while(( line = br.readLine()) != null ) {
            Matcher m = _section.matcher( line );
            if( m.matches()) {
               section = m.group( 1 ).trim();
            }
            else if( section != null ) {
               m = _keyValue.matcher( line );
               if( m.matches()) {
                  String key   = m.group( 1 ).trim();
                  String value = m.group( 2 ).trim();
                  Map< String, String > kv = _entries.get( section );
                  if( kv == null ) {
                     _entries.put( section, kv = new HashMap<>());   
                  }
                  kv.put( key, value );
               }
            }
         }
      }
   }

   public String getString( String section, String key, String defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return kv.get( key );
   }

   public int getInt( String section, String key, int defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return Integer.parseInt( kv.get( key ));
   }

   public float getFloat( String section, String key, float defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return Float.parseFloat( kv.get( key ));
   }

   public double getDouble( String section, String key, double defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return Double.parseDouble( kv.get( key ));
   }
}
Aerospace
  • 1,210
  • 11
  • 18
  • +1 Simply for use of regex Pattern/Matcher. Works like a charm – kalelien Aug 22 '13 at 17:48
  • Not a perfect solution but a good starting point, e.g., missing getSection() and getString() only returns defaultValue if whole section is missing. – Jack Miller Feb 25 '15 at 08:41
  • what is performance difference between such a regx vs working with string implementation? – Ewoks Sep 29 '15 at 21:48
  • Performance when reading a small configuration file isn't a concern. opening and closing a file is much more consuming, I believe. – Aerospace Sep 30 '15 at 12:27
  • Yep, this is about as simple as it should be for simple use cases. Not sure why people want to complicate it. If you're concerned about performance (or other concerns like error reporting), yeah, you probably want to use something else (probably another format entirely). – User Aug 24 '16 at 02:38
  • Don't reinvent the wheel. This is as standard problem as it ever gets a suggesting maintaining its solution in every codebase that needs to solve it instead of building and using a public library is anti-progress. – Daddy32 Apr 12 '20 at 13:42
17

Here's a simple, yet powerful example, using the apache class HierarchicalINIConfiguration:

HierarchicalINIConfiguration iniConfObj = new HierarchicalINIConfiguration(iniFile); 

// Get Section names in ini file     
Set setOfSections = iniConfObj.getSections();
Iterator sectionNames = setOfSections.iterator();

while(sectionNames.hasNext()){

 String sectionName = sectionNames.next().toString();

 SubnodeConfiguration sObj = iniObj.getSection(sectionName);
 Iterator it1 =   sObj.getKeys();

    while (it1.hasNext()) {
    // Get element
    Object key = it1.next();
    System.out.print("Key " + key.toString() +  " Value " +  
                     sObj.getString(key.toString()) + "\n");
}

Commons Configuration has a number of runtime dependencies. At a minimum, commons-lang and commons-logging are required. Depending on what you're doing with it, you may require additional libraries (see previous link for details).

Ewoks
  • 11,626
  • 6
  • 53
  • 65
user50217
  • 179
  • 1
  • 2
14

Or with standard Java API you can use java.util.Properties:

Properties props = new Properties();
try (FileInputStream in = new FileInputStream(path)) {
    props.load(in);
}
hoat4
  • 1,053
  • 12
  • 9
Peter
  • 5,686
  • 18
  • 23
  • 14
    Problem is that, with ini files, the structure has headers. The Property class does not know how to handle the headers, and there could be name clashes – Mario Ortegón Oct 13 '08 at 12:05
  • 2
    Also, the `Properties` class does not properly get values that contain \ – rds Dec 05 '11 at 15:40
  • 3
    +1 for the simple solution, but fits only simple config files, as Mario Ortegon and rds noticed it. – Benj Dec 05 '12 at 00:43
  • 1
    INI file contains [section], properties file contains assignments. – Aerospace Aug 27 '13 at 11:56
  • using Properties: [java tutorial](http://docs.oracle.com/javase/tutorial/essential/environment/properties.html) – n611x007 Sep 19 '13 at 10:56
  • 1
    file format: 1/ [a simple line-oriented](http://docs.oracle.com/javase/6/docs/api/java/util/Properties.html#load%28java.io.Reader%29) or 2/ [a simple XML format](http://docs.oracle.com/javase/6/docs/api/java/util/Properties.html#loadFromXML%28java.io.InputStream%29) or 3/ [a simple line-oriented, using ISO 8859-1](http://docs.oracle.com/javase/6/docs/api/java/util/Properties.html#load%28java.io.InputStream%29) (with [Unicode escapes](http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.3) + use `native2ascii` for other encodings) – n611x007 Sep 30 '13 at 13:10
  • Questio is specifically for INI file which has Sections.Properties class can not handle Sections. – Vikas Piprade Sep 14 '20 at 13:26
11

In 18 lines, extending the java.util.Properties to parse into multiple sections:

public static Map<String, Properties> parseINI(Reader reader) throws IOException {
    Map<String, Properties> result = new HashMap();
    new Properties() {

        private Properties section;

        @Override
        public Object put(Object key, Object value) {
            String header = (((String) key) + " " + value).trim();
            if (header.startsWith("[") && header.endsWith("]"))
                return result.put(header.substring(1, header.length() - 1), 
                        section = new Properties());
            else
                return section.put(key, value);
        }

    }.load(reader);
    return result;
}
hoat4
  • 1,053
  • 12
  • 9
2

You could try JINIFile. Is a translation of the TIniFile from Delphi, but for java

https://github.com/SubZane/JIniFile

Andreas Norman
  • 953
  • 1
  • 8
  • 18
2

I personally prefer Confucious.

It is nice, as it doesn't require any external dependencies, it's tiny - only 16K, and automatically loads your ini file on initialization. E.g.

Configurable config = Configuration.getInstance();  
String host = config.getStringValue("host");   
int port = config.getIntValue("port"); 
new Connection(host, port);
Mark
  • 150
  • 1
  • 10
2

Another option is Apache Commons Config also has a class for loading from INI files. It does have some runtime dependencies, but for INI files it should only require Commons collections, lang, and logging.

I've used Commons Config on projects with their properties and XML configurations. It is very easy to use and supports some pretty powerful features.

John Meagher
  • 20,560
  • 13
  • 50
  • 57
0

hoat4's solution is very elegant and simple. It works for all sane ini files. However, I have seen many that have un-escaped space characters in the key.
To solve this, I have downloaded and modified a copy of java.util.Properties. Though this is a little unorthodox, and short-term, the actual mods were but a few lines and quite simple. I will be puting forward a proposal to the JDK community to include the changes.

By adding an internal class variable:

private boolean _spaceCharOn = false;

I control the processing related to scanning for the key/value separation point. I replaced the space characters search code with a small private method that returns a boolean depending on the state of the above variable.

private boolean isSpaceSeparator(char c) {
    if (_spaceCharOn) {
        return (c == ' ' || c == '\t' || c == '\f');
    } else {
        return (c == '\t' || c == '\f');
    }
}

This method is used in two places within the private method load0(...).
There is also a public method to switch it on, but it would be better to use the original version of Properties if the space separator is not an issue for your application.

If there is interest, I would be willing to post the code to my IniFile.java file. It works with either version of Properties.

0

Using answer by @Aerospace, I realized that it is legitimate for INI files to have sections without any key-values. In this case, addition to the top-level map should happen before any key-values are found, ex (minimally updated for Java 8):

            Path location = ...;
            try (BufferedReader br = new BufferedReader(new FileReader(location.toFile()))) {
                String line;
                String section = null;
                while ((line = br.readLine()) != null) {
                    Matcher m = this.section.matcher(line);
                    if (m.matches()) {
                        section = m.group(1).trim();
                        entries.computeIfAbsent(section, k -> new HashMap<>());
                    } else if (section != null) {
                        m = keyValue.matcher(line);
                        if (m.matches()) {
                            String key = m.group(1).trim();
                            String value = m.group(2).trim();
                            entries.get(section).put(key, value);
                        }
                    }
                }
            } catch (IOException ex) {
                System.err.println("Failed to read and parse INI file '" + location + "', " + ex.getMessage());
                ex.printStackTrace(System.err);
            }

denka
  • 3
  • 1
-3

It is just as simple as this.....

//import java.io.FileInputStream;
//import java.io.FileInputStream;

Properties prop = new Properties();
//c:\\myapp\\config.ini is the location of the ini file
//ini file should look like host=localhost
prop.load(new FileInputStream("c:\\myapp\\config.ini"));
String host = prop.getProperty("host");