Simple Usage Examples

This page does some walk-throughs of simple code examples. You might try the Live-Code Quickstart to try using AndHow right in the browser. The Live-Code is more fun, but this page is much more comprehensive.

Complete Usage Example

package simple;
import org.yarnandtail.andhow.property.*;

public class HelloWorld {

  // 1 Declare AndHow Properties
  private static final StrProp NAME = StrProp.builder().defaultValue("Dave").build();
  public static final IntProp REPEAT_COUNT = IntProp.builder().defaultValue(2).build();

  public static void main(String[] args) {

    // 2 Use AndHow Properties
    for (int i = 0; i < REPEAT_COUNT.getValue(); i++) {
      System.out.println("Hello, " + NAME.getValue());
    }
  }
}

>> Complete code <<

// 1 : Declare AndHow Properties

StrProp & IntProp are AndHow Propertys. Properties and their values are constants, so they are always static final but may be private or any scope. Properties are strongly typed, so their value, default value and validation are type specific.

// 2 : Using AndHow Properties

Properties are used just like static final constants with .getValue() tacked on: NAME.getValue() returns a String, REPEAT_COUNT.getValue() returns an Integer.

The Properties in the example have defaults, so with no other configuration, running HelloWorld.main() will print Hello, Dave twice. One way (of many) to configure values is to add an andhow.properties file on the classpath like this:

# andhow.properties file at the classpath root

simple.HelloWorld.NAME: Kathy
SIMPLE.HELLOWWORLD.repeat_count: 4

Resulting in Hello, Kathy x4 - AndHow ignores capitalization when reading Property values. Unlike most configuration utilities, we didn't have to specify a 'name' for NAME. AndHow builds a logical name for each property by combining the canonical name of the containing class with the variable name, e.g.: [Java canonical class name].[AndHow Property name] --> simple.HelloWorld.NAME Thus, naming isn't something you have to worry about and Java itself ensures name uniqueness. Let's extends this example a bit...

Adding validation and command line arguments

package simple;  // Imports left out for simplicity

public class HelloWorld2 {

  // 1 Declare
  private static interface Config {
    StrProp NAME = StrProp.builder().mustStartWith("D").defaultValue("Dave").build();
    IntProp REPEAT_COUNT = IntProp.builder()mustBeGreaterThan(0)
      .mustBeLessThan(5).defaultValue(2).build();
  }

  public static void main(String[] args) {

    AndHow.findConfig().setCmdLineArgs(args);  //2 Add cmd line arguments

    // Use
    for (int i = 0; i < Config.REPEAT_COUNT.getValue(); i++) {
      System.out.println("Hello, " + Config.NAME.getValue());
    }
  }
}

>> Complete code <<

// 1 : Declare AndHow Properties with validation

Property values can have validation. At startup, AndHow discovers and validates all Properties in your entire application, ensuring that a mis-configuration application fails fast at startup, rather than mysteriously failing later.

Placing Property's in an interface is best practice for organization and access control. Only code able to 'see' a Property can retrieve its value - standard Java visibility rules. Fields in an interface are implicitly static final, saving a bit of typing.

// 2 : Add command line arguments

AndHow loads Property values from several configuration sources in a well established order. At startup, AndHow scans System.Properties, environment variables, JNDI values, the andhow.properties file, etc., automatically.

Reading from commandline requires a bit of help from the application. The code AndHow.findConfig().setCmdLineArgs(args); passes the command line arguments in to AndHow.

Running from cmd line to set NAME to 'Dar' would look like this:

  java -Dsimple.HelloWorld2.Config.NAME=Dar -cp [classpath] simple.HelloWorld2

What happens if we try to set NAME to "Bar" and violate the 'D' validation rule? AndHow throws a RuntimeException to stop app startup and prints a clear message about the cause:

================================================================================
== Problem report from AndHow!  ================================================
================================================================================
Property simple.HelloWorld2.Config.NAME loaded from string key value pairs:
  The value 'Bar' must start with 'D'
================================================================================
A configuration template for this application has been written to: [...tmp file location...]

AndHow uses Property metadata to generate precise error messages. When errors prevent startup, AndHow also creates a configuration template with all your application's Propertys, validation requirements, types, defaults and more. Here is what that would look like for this app:

==/==/==/==/==/== Excerpt from a configuration template ==/==/==/==/==/==
# NAME (String)
# Default Value: Dave
# The property value must start with 'D'
simple.HelloWorld2.Config.NAME = Dave

# REPEAT_COUNT (Integer)
# Default Value: 2
# The property value must be less than 5
simple.HelloWorld2.Config.REPEAT_COUNT = 2

You can create a configuration template on demand by setting the AHForceCreateSamples flag: java -DAHForceCreateSamples=true -cp [classpath] simple.HelloWorld2

Let's look at a larger, more enterprise-y example.

Example with a database connection, aliases and exports

In this example, assume we need to configure an ORM framework like Hibernate. 3rd party frameworks have their own configuration property names and typically accept properties as a Map or util.Properties. This example is in two parts: A Handler class which might be an AWS Lambda, and a DAO (Data Access Object) which stores data to a DB using Hibernate.

package simple;  // Both classes are in 'simple'.  Imports left out for simplicity.

public class SaleHandler {

  // 1 Declare configuration Property's for this class
  public static interface Config {
    BigDecProp TAX_RATE = BigDecProp.builder().mustBeGreaterThan(BigDecimal.ZERO)
        .mustBeNonNull().desc("Tax rate as a decimal, eg .10").aliasIn("tax").build();
  }

  public Object handle(BigDecimal saleAmount) throws Exception {

    // TAX_RATE.getValue() returns a BigDecimal
    BigDecimal tax = saleAmount.multiply(Config.TAX_RATE.getValue());
    return new SaleDao().storeSale(saleAmount.add(tax));
  }
}

>> Complete code <<

// 1 : Declare configuration Property's for this class

AndHow best practice: Place Properties in the class that uses them. This makes intuitive sense and there is no need to gather them all into a central Config class - AndHow will find, load and validate them all and create a configuration template listing them all.

The TAX_RATE Property uses .aliasIn("tax"), which adds an alternate name recognized when reading this property from a configuration source. Handy for values that may need to be specified on cmd line.

Lets look at how the DAO class uses AndHow:

public class SaleDao {

  // 2 Declare DB connection Properties
  @ManualExportAllowed
  private static interface Db {
    StrProp URL = StrProp.builder().mustStartWith("jdbc://").mustBeNonNull()
        .aliasInAndOut("hibernate.connection.url").build();
    StrProp PWD = StrProp.builder().mustBeNonNull()
        .aliasInAndOut("hibernate.connection.password").build();
  }

  // 3 Export Db properties to java Properties instance
  java.util.Properties getExportedConfig() throws Exception {
    Properties props = AndHow.instance().export(Db.class)
        .collect(ExportCollector.stringProperties(""));

    return props;
  }

  // Pretend database storage call
  Object storeSale(Object sale) throws Exception {
    Hibernate h = new Hibernate(getExportedConfig());
    return h.save(sale);
  }
}

>> Complete code <<

// 3 : Declare DB connection Properties

The Properties bundled together in Db are annotated with @ManualExportAllowed, allowing them to be exported to a Map or other structure. .aliasInAndOut() adds an in name just like 'tax' above, but the name is also used when exporting (out).

// 4 : Export Db properties to a java.util.Properties instance

Property exports use the Java stream() API. Exports are done at the class level: AndHow.instance().export(class...) specifies one or more @ManualExportAllowed annotated classes containing AndHow Properties. ExportCollector has collectors to turn a stream of Properties into collections: ExportCollector.stringProperties("") turns AndHow Properties into java.util.Properties with String values (using "" for null values).

Since we specified alias out names for the Db Properties, the Hibernate compatible aliases are used for the export. AndHow provides validation at startup, configuration from multiple sources, and more... and can do that for 3rd party frameworks!

Testing Applications with AndHow

AndHow makes testing with multiple configurations easy. Let's test the SaleHandler from above and assume an `andhow.properties' file like this:

simple.SaleDao.Db.PWD = changeme
simple.SaleDao.Db.URL = jdbc://mydb
simple.SaleHandler.Config.TAX_RATE = .11

We can verify the tax rate is set as expected:

  @Test  //This verifies that the tax rate is .11 from the andhow.properties file
  public void defaultConfigTaxRateShouldBe_11Percent() throws Exception {
    SaleHandler handler = new SaleHandler();

    // Total sale should be 10.00 + (10.00 * .11)
    assertEquals(new BigDecimal("11.10"), handler.handle(BigDecimal.TEN));
  }

Now lets test the handler with the tax rate configured to 12%:

  @Test @KillAndHowBeforeThisTest  // 1 'Kill' the current AndHow configuration
  public void verifyTaxRateAt_12Percent() throws Exception {

    // 2 Set a new configured value for TAX_RATE
    AndHow.findConfig().addFixedValue(SaleHandler.Config.TAX_RATE, new BigDecimal(".12"));

    SaleHandler handler = new SaleHandler();

    // Total sale should be 10.00 + (10.00 * .12)
    assertEquals(new BigDecimal("11.20"), handler.handle(BigDecimal.TEN));
  }  // 3 Cleanup after the test

>> Complete code <<

// 1 : 'Kill' the current AndHow configuration

The annotation @KillAndHowBeforeThisTest erases AndHow's state before the test.

// 2 : Set a new configured value for TAX_RATE

AndHow.findConfig() grabs the configuration of AndHow itself to add a 'fixed value' for TAX_RATE, ignoring any other configured value for that property.

// 3 : Cleanup after the test

The tax rate is .12 just for this test. When the test is done, AndHow is restored to its previous state. This isn't allowed in production: Remember, AndHow Property values are constant and once initialized at startup, do not change. The @KillAndHow... annotation uses reflection to bend the rules to make testing easy.

&?!

Last updated