Category Archives: Java

JEE Security: Preventing Clickjacking Attacks

PROBLEM

Clickjacking is an attack that tricks the users to perform unintended actions… see OWASP’s Testing for Clickjacking (OTG-CLIENT-009)

SOLUTION

To prevent clickjacking attacks, the app must set X-FRAME-OPTIONS header with an appropriate value:-

  • DENY: this denies any domain using the page as an iFrame source. This is the best option.
  • SAMEORIGIN: this allows pages within the same domain to use other application pages as iFrame sources.
  • ALLOW-FROM [whitelisted domains]: this declares a list of domains that are allowed to include the pages as iFrame sources.

If set correctly, the HTTPS response should show X-FRAME-OPTIONS header:-

➜  ~ curl -i -k https://localhost:8443/
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-Application-Context: application:local:8443
Set-Cookie: JSESSIONID=04ADDAF886A20AA561021E869E980BCC; Path=/; Secure; HttpOnly
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Content-Length: 631
Date: Thu, 31 Aug 2017 14:56:57 GMT

There are several ways to set this header.

Solution 1: Using a servlet filter

You may create a servlet filter that sets X-FRAME-OPTIONS in the response header.

Here’s an example using web.xml-less Spring Boot:-

@SpringBootApplication
class Application extends SpringBootServletInitializer {
    static void main(String[] args) {
        SpringApplication.run(Application, args)
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Application)
    }

    @Bean
    FilterRegistrationBean clickjackingPreventionFilter() {
        return new FilterRegistrationBean(
                urlPatterns: ['/**'],
                filter: new Filter() {
                    @Override
                    void init(final FilterConfig filterConfig) throws ServletException {
                    }

                    @Override
                    void doFilter(final ServletRequest servletRequest,
                                  final ServletResponse servletResponse,
                                  final FilterChain filterChain) throws IOException, ServletException {
                        final HttpServletResponse response = (HttpServletResponse) servletResponse
                        response.addHeader('X-FRAME-OPTIONS', 'DENY')
                        filterChain.doFilter(servletRequest, servletResponse)
                    }

                    @Override
                    void destroy() {
                    }
                }
        )
    }
}

Solution 2: Using Spring Security

Spring Security provides a very easy way to set the X-FRAME-OPTIONS header:-

@Configuration
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http.
                headers().frameOptions().deny(). 
                and().
                authorizeRequests().
                antMatchers('/**').permitAll()
    }
}
Advertisements

JEE Security: Disabling HTTP OPTIONS method

PROBLEM

HTTP OPTIONS method is used to provide a list of methods that are supported by the web server.

For example, the following shows both GET and HEAD are allowed on the given link:-

➜  ~ curl -i -k -X OPTIONS https://localhost:8443/
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-Application-Context: application:local:8443
Allow: GET,HEAD
Content-Length: 0
Date: Thu, 31 Aug 2017 14:07:21 GMT

Enabling OPTIONS may increase the risk of cross-site tracing (XST)… see OWASP’s Test HTTP Methods (OTG-CONFIG-006).

SOLUTION

There are several ways to disable OPTIONS method.

Solution 1: Using web.xml

If your app has web.xml, you may add the following snippet:-

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
		 http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         metadata-complete="true" version="3.1">

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>restricted methods</web-resource-name>
            <url-pattern>/*</url-pattern>
            <http-method>OPTIONS</http-method>
        </web-resource-collection>
        <auth-constraint/>
    </security-constraint>
	
    <!-- Other configurations -->
</web-app>

Solution 2: Using Spring Boot

If you are using Spring Boot, there isn’t any option to mimic the above configuration programmatically.

However, you still can use web.xml in conjunction with Spring Boot by setting metadata-complete to false and use servlet version 3.0 or higher:-

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
		 http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         metadata-complete="false" version="3.1">

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>restricted methods</web-resource-name>
            <url-pattern>/*</url-pattern>
            <http-method>OPTIONS</http-method>
        </web-resource-collection>
        <auth-constraint/>
    </security-constraint>
</web-app>

Solution 3: Using Spring Security

If you don’t want to use web.xml, you may configure Spring Security to disable OPTIONS method on all URIs:-

@Configuration
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http.authorizeRequests().
                antMatchers(HttpMethod.OPTIONS, '/**').denyAll().
                antMatchers('/**').permitAll()
    }
}

Now, when trying to hit the same link with OPTIONS method, the app will return 403 Forbidden:-

➜  ~ curl -i -k -X OPTIONS https://localhost:8443/
HTTP/1.1 403
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 31 Aug 2017 14:26:51 GMT

Java: Exploring Preferences API

BACKGROUND

In any written scripts or rich client apps, there is almost a need to persist the user preferences or app configurations.

Most of the time, we, the proud developers, handle that situation in very ad-hoc manner. When storing in a file, we use different formats: from old-boring XML, to cool-kid JSON, to even cooler-kid YAML or the kindergarten-kid key=value property. Then, we have to decide where to write the file to, whether to use C:\ and screw your non-windows users, whether to use backslashes to construct the file path or forward slashes because we are sick and tired escaping the effing backslashes.

The long story short is… yes, we, the proud developers, can do all that… or, as one of my current project peers like to say, “make it configurable” on literally everything to the point it’s pretty close of becoming a drinking game now.

But, the point I want to make here is… we are consistent on being inconsistent.

SOLUTION

Java provides the Preferences API as an attempt to solve this mess. Using this API, the developers do not need to know where or how to store the user preferences or app configurations. Rather, it relies on the native API to store the data: registry for Windows, .plist for Mac and XML for Unix/Linux.

The most interesting part is… the Preferences API has been around since JDK 1.4.

Code wise, it doesn’t get any simpler than this:-

// create new configuration or reference existing configuration
Preferences preferences = Preferences.userNodeForPackage(WuTangClan)

// insert/update 3 key/value pairs
preferences.put('key1', 'value1')
preferences.put('key2', 'value2')
preferences.put('key3', 'value3')

// returns 'value2'
println preferences.get('key2', '-')

// returns '-'
println preferences.get('invalid', '-')

// remove by key
preferences.remove('key3')

// delete everything
preferences.removeNode()

But, where and how exactly do Mac and Windows store this data?

There are several ways to get an instance of Preferences.

Preferences.userNodeForPackage(WuTangClan)

Mac

If WuTangClan class file is located under wu.tang.clan.config package, the configuration file is created at ~/Library/Preferences/wu.tang.clan.plist with the following content:-

{    "/wu/tang/clan/" = {
        "config/" = {
            "key1" = "value1";
            "key2" = "value2";
        };
    };
}

64-bit Windows + 64-bit JVM

Configuration is stored in the registry with the following key:-

HKEY_CURRENT_USER\SOFTWARE\JavaSoft\Prefs\wu\tang\clan\config

Preferences.userRoot().node(‘path’)

Example 1

Let’s assume we have this:-

Preferences.userRoot().node('wu')

Mac

The configuration is created at ~/Library/Preferences/com.apple.java.util.prefs.plist with the following content:-

{    "/" = {
        ...
        
        "wu/" = {
            "key1" = "value1";
            "key2" = "value2";
        };
        
        ...
    };
}

This file also contains configurations from other installed software.

64-bit Windows + 64-bit JVM

Configuration is stored in the registry with the following key:-

HKEY_CURRENT_USER\SOFTWARE\JavaSoft\Prefs\wu

Example 2

How about this?

Preferences.userRoot().node('wu/tang')

// ... OR ...

Preferences.userRoot().node('wu').node('tang')

Mac

The configuration still resides under ~/Library/Preferences/com.apple.java.util.prefs.plist with the following content:-

{    "/" = {
        ...
        "wu/" = {
            "tang/" = {
                "key1" = "value1";
                "key2" = "value2";
            };
        };
        ...
    };
}

64-bit Windows + 64-bit JVM

Configuration is stored in the registry with the following key:-

HKEY_CURRENT_USER\SOFTWARE\JavaSoft\Prefs\wu\tang

Example 3

How about this?

Preferences.userRoot().node('wu/tang/clan')

// ... OR ...

Preferences.userRoot().node('wu').node('tang').node('clan')

Mac

Now, the shit is about to get real here.

Mac, for some reason, creates a stub under ~/Library/Preferences/com.apple.java.util.prefs.plist with the following content:-

{    "/" = {
        ...
        "wu/" = { "tang/" = { "clan/" = { }; }; };
        ...
    };
}

The actual configuration now resides under ~/Library/Preferences/wu.tang.clan.plist:-

{    "/wu/tang/clan/" = {
        "key1" = "value1";
        "key2" = "value2";
    };
}

It appears when the path reaches certain depth, Mac will create the separate configuration file for it.

64-bit Windows + 64-bit JVM

Configuration is stored in the registry with the following key:-

HKEY_CURRENT_USER\SOFTWARE\JavaSoft\Prefs\wu\tang\clan

Preferences.systemNodeForPackage(WuTangClan) or Preferences.systemRoot().node(‘path’)

Instead of storing the configuration at user level, we may also store the configuration at system level.

Mac

Instead of storing under ~/Library/Preferences, the configuration is stored under /Library/Preferences.

On top of that, based on Java Development Guide for Mac, the configuration is only persisted if the user is an administrator.

The really weird part is the code will not throw any exceptions due to insufficient permission.

64-bit Windows + 64-bit JVM

Instead of storing under HKEY_CURRENT_USER\[path], the configuration is stored under HKEY_LOCAL_MACHINE\[path].

Best Practices

I’m not sure if this is a best practice, but my personal preference is to specify my own string path through Preferences.userRoot().node(..).

Preferences.userNodeForPackage(..) worries me because if I refactor my code by moving the class files around, it may not find the existing configuration due to changed path.

When specifying the string path, do make sure the path value is rather unique to prevent reading an existing configuration from other installed software.

JAXB2: Adding toString() to Generated Java Classes

PROBLEM

By default, the generated Java class prints the memory address when toString() is invoked.

However, sometimes it is helpful to have a more meaningful toString() for debugging purposes.

SOLUTION

To fix this, configure maven-jaxb2-plugin to generate toString() based on the fields in the class:-

<project ...>
  ...
  
  <properties>
    <org.jvnet.jaxb2_commons.version>0.11.1</org.jvnet.jaxb2_commons.version>
  </properties>
  
  <dependencies>
    <dependency>
      <groupId>org.jvnet.jaxb2_commons</groupId>
      <artifactId>jaxb2-basics-runtime</artifactId>
      <version>${org.jvnet.jaxb2_commons.version}</version>
    </dependency>
  </dependencies>
  
  <build>
    <plugins>
      <plugin>
        <groupId>org.jvnet.jaxb2.maven2</groupId>
        <artifactId>maven-jaxb2-plugin</artifactId>
        <version>0.13.1</version>
        <executions>
          <execution>
            <goals>
              <goal>generate</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <schemaLanguage>WSDL</schemaLanguage>
          <generatePackage>my.package.wsdl</generatePackage>
          <bindingDirectory>${project.basedir}/src/main/resources</bindingDirectory>
          <bindingIncludes>
            <include>jaxb-binding.xjb</include>
          </bindingIncludes>
          <schemas>
            <schema>
              <fileset>
                <directory>${project.basedir}/src/main/resources</directory>
                <includes>
                  <include>web-service.wsdl</include>
                </includes>
              </fileset>
            </schema>
          </schemas>
          <args>
            <arg>-XtoString</arg>
          </args>
          <plugins>
            <plugin>
              <groupId>org.jvnet.jaxb2_commons</groupId>
              <artifactId>jaxb2-basics</artifactId>
              <version>${org.jvnet.jaxb2_commons.version}</version>
            </plugin>
          </plugins>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Java + HTTPS: Unable to Find Valid Certification Path to Requested Target

PROBLEM

When invoking a HTTPS URL from Java, for example…

final RestTemplate restTemplate = new RestTemplate();
restTemplate.execute("https://www.google.com/some-api", HttpMethod.GET, null, ...);

…the following exception is thrown…

org.springframework.web.client.ResourceAccessException: I/O error on GET 
request for "https://www.google.com":sun.security.validator.ValidatorException: 
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target; nested exception 
is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: 
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:607)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:557)

SOLUTION 1: Disable SSL Validation – NOT RECOMMENDED

One way is to simply disable the SSL validation by configuring SSLContext to trust all X.509 certificates before invoking the intended HTTPS URL.

@Configuration
class MyAppConfig {
    @Bean
    public Boolean disableSSLValidation() throws Exception {
        final SSLContext sslContext = SSLContext.getInstance("TLS");

        sslContext.init(null, new TrustManager[]{new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }}, null);

        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });

        return true;
    }
}

Unless you are writing test cases or implementing non-production code, this approach is highly discouraged because it doesn’t prevent the man-in-the-middle attacks.

SOLUTION 2: Import Certificate into Java Keystore – RECOMMENDED

The reason we see this exception is because the certificate used by the HTTPS URL doesn’t exist in the Java Keystore. Hence, the SSL validation fails.

To fix this, we can download the website’s certificate using a browser to be added into Java Keystore.

Access the website (in this example, I use https://www.google.com) and click on the pad lock.

Find the button that allows you to view and export the certificate as a PEM file. In this example, I named that file google.cer.

The content of the downloaded PEM file should look like this:-

-----BEGIN CERTIFICATE-----
MIIEgDCCA2igAwIBAgIITq9JKLrGf5EwDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE
BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl
eoV23mHmV6/0mOwocVYt/Th96WNGGmhANkFW//HCphRWnhaOqIG6yFRQ/jxArTvZ
QJEGI5AiYHzQn7LdUM8mH1o3ifR+lX+QiAwyeU9oegdlRslI2KMoPOuOFj329NFx
Bw+XVQXMsRJITPg8pnegPmLCOjpz8y7pBxbxGnfaI66I8X4dArsaXX4r5mkfhk2e
mm7fxQ8qUaW9mKoW0XvwGxU0AwKI8OopuXHoD97vr2GSK0QNZ19A96mtTWnQ2cu2
i9qjGw==
-----END CERTIFICATE-----

Go to the Terminal.

If you have multiple versions of java, make sure you choose the correct version first:-

export JAVA_HOME="`/usr/libexec/java_home -v '1.7*'`"

Import the certificate into Java Keystore:-

sudo keytool -import -trustcacerts -file /path/to/google.cer -alias google -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit

Now, you should be able to invoke the HTTPS URL without any exception.

If for some reason you need to remove the imported certificate from Java Keystore, you can refer to that certificate using the alias and run this command:-

sudo keytool -delete -alias google -keystore $JAVA_HOME/jre/lib/security/cacerts

Java + HTTPS: Handling ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY

PROBLEM

When accessing HTTPS links from a local application server, the modern browser throws the following error message(s):-

  • SSL received a weak ephemeral Diffie-Hellman key in Server Key Exchange handshake message. (Error code: ssl_error_weak_server_ephemeral_dh_key)
  • Server has a weak ephemeral Diffie-Hellman public key

SOLUTIONS

There are multiple solutions to this problem.

SOLUTION 1: Disable browser check

One way is to completely disable this check on the browser.

For example, in Firefox, go to about:config and set security.ssl3.dhe_rsa_aes_128_sha and security.ssl3.dhe_rsa_aes_256_sha to false.

The downside is the browser is exposed to Logjam attacks.

SOLUTION 2: Use Java 8

The easiest solution is to use Java 8 because it has better cipher support.

The downside is sometimes using Java 8 is not an option if you don’t have control on the application server.

SOLUTION 3: Use Bouncy Castle

If you are using Java 6 or Java 7, you can download Bouncy Castle crypto implementation and store them in $JAVA_HOME/jre/lib/ext.

Then, register this provider in $JAVA_HOME/jre/lib/security/java.security, for example:-

#
# List of providers and their preference orders (see above):
#
# MacOSX added com.apple.crypto.provider.Apple as #3
security.provider.1=sun.security.pkcs11.SunPKCS11 ${java.home}/lib/security/sunpkcs11-macosx.cfg
security.provider.2=sun.security.provider.Sun
security.provider.3=com.apple.crypto.provider.Apple
security.provider.4=sun.security.rsa.SunRsaSign
security.provider.5=com.sun.net.ssl.internal.ssl.Provider
security.provider.6=com.sun.crypto.provider.SunJCE
security.provider.7=sun.security.jgss.SunProvider
security.provider.8=com.sun.security.sasl.Provider
security.provider.9=org.jcp.xml.dsig.internal.dom.XMLDSigRI
security.provider.10=sun.security.smartcardio.SunPCSC
security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider

The downside is if your team is doing local application development, all team members have to perform these steps on their machines too.

SOLUTION 4: Disable all weak Diffie-Hellman ciphers

Another solution is to configure the server by disabling all the weak Diffie-Hellman ciphers.

You can either enable the strong ciphers or disable the weak ciphers.

However, it is preferable (in my opinion) to disable weak ciphers so that newer (and stronger) ciphers will automatically be included when Java version is updated in the future.

Here is a working example for Jetty server:-

<plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>8.1.16.v20140903</version>
    <configuration>
        <stopKey>jetty-stop-key</stopKey>
        <stopPort>7778</stopPort>
        <scanIntervalSeconds>1</scanIntervalSeconds>
        <requestLog implementation="org.eclipse.jetty.server.NCSARequestLog">
            <extended>true</extended>
            <logTimeZone>CST</logTimeZone>
        </requestLog>
        <webApp>
            <contextPath>/${project.artifactId}</contextPath>
            <webInfIncludeJarPattern>.*/.*jsp-api-[^/]\.jar$|./.*jsp-[^/]\.jar$|./.*taglibs[^/]*\.jar$</webInfIncludeJarPattern>
        </webApp>
        <connectors>
            <connector implementation="org.eclipse.jetty.server.bio.SocketConnector">
                <port>7777</port>
            </connector>
            <connector implementation="org.eclipse.jetty.server.ssl.SslSocketConnector">
                <port>7443</port>
                <keystore>${project.basedir}/src/main/config/keystore.jks</keystore>
                <keyPassword>jetty8</keyPassword>
                <password>jetty8</password>
                <excludeCipherSuites>
                    <excludeCipherSuites>SSL_RSA_WITH_DES_CBC_SHA</excludeCipherSuites>
                    <excludeCipherSuites>SSL_DHE_RSA_WITH_DES_CBC_SHA</excludeCipherSuites>
                    <excludeCipherSuites>SSL_DHE_DSS_WITH_DES_CBC_SHA</excludeCipherSuites>
                    <excludeCipherSuites>SSL_RSA_EXPORT_WITH_RC4_40_MD5</excludeCipherSuites>
                    <excludeCipherSuites>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</excludeCipherSuites>
                    <excludeCipherSuites>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</excludeCipherSuites>
                    <excludeCipherSuites>SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA</excludeCipherSuites>
                    <excludeCipherSuites>SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA</excludeCipherSuites>
                    <excludeCipherSuites>TLS_DHE_RSA_WITH_AES_256_CBC_SHA256</excludeCipherSuites>
                    <excludeCipherSuites>TLS_DHE_DSS_WITH_AES_256_CBC_SHA256</excludeCipherSuites>
                    <excludeCipherSuites>TLS_DHE_RSA_WITH_AES_256_CBC_SHA</excludeCipherSuites>
                    <excludeCipherSuites>TLS_DHE_DSS_WITH_AES_256_CBC_SHA</excludeCipherSuites>
                    <excludeCipherSuites>TLS_DHE_RSA_WITH_AES_128_CBC_SHA256</excludeCipherSuites>
                    <excludeCipherSuites>TLS_DHE_DSS_WITH_AES_128_CBC_SHA256</excludeCipherSuites>
                    <excludeCipherSuites>TLS_DHE_RSA_WITH_AES_128_CBC_SHA</excludeCipherSuites>
                    <excludeCipherSuites>TLS_DHE_DSS_WITH_AES_128_CBC_SHA</excludeCipherSuites>
                </excludeCipherSuites>
            </connector>
        </connectors>
    </configuration>
</plugin>

The downside is the server configuration gets a little lengthy.

Java: Builder for Immutable POJO

PROBLEM

Let’s assume we have an immutable Person object:-

public final class Person implements Serializable {
    private final String name;
    private final Collection<Car> cars;

    public Person(final String name, final Collection<Car> cars) {
        this.name = name;
        this.cars = MoreObjects.firstNonNull(cars, ImmutableList.<Car>of());
    }

    public String getName() {
        return name;
    }

    public Collection<Car> getCars() {
        return cars;
    }
}

… and an immutable Car object:-

public final class Car implements Serializable {
    private final String license;
    private final LocalDate boughtOn;

    public Car(final String license, final LocalDate boughtOn) {
        this.license = license;
        this.boughtOn = boughtOn;
    }

    public String getLicense() {
        return license;
    }

    public LocalDate getBoughtOn() {
        return boughtOn;
    }
}

To create the Person object, we need to write something like this:-

final Person person = new Person("Mike",
                                 Collections.singletonList(
                                     new Car("123", new LocalDate(2015, 1, 1))));

If the POJO has a lot of properties, it becomes very difficult to keep track all the constructor argument positions.

SOLUTION

@mkarneim wrote a POJO Builder that creates the builder class(es) at compilation time.

To pull this off, we need to add the following dependency with provided scope.

<dependency>
    <groupId>net.karneim</groupId>
    <artifactId>pojobuilder</artifactId>
    <version>3.4.0</version>
    <scope>provided</scope>
</dependency>

Next, annotate with the Person constructor with @GeneratePojoBuilder. Make the constructor visibility to protected so that no one can directly instantiate that object.

public final class Person implements Serializable {
    private final String name;
    private final Collection<Car> cars;

    @GeneratePojoBuilder
    protected Person(final String name, final Collection<Car> cars) {
        this.name = name;
        this.cars = MoreObjects.firstNonNull(cars, ImmutableList.<Car>of());
    }

    public String getName() {
        return name;
    }

    public Collection<Car> getCars() {
        return cars;
    }
}

Do the same for Car object:-

public final class Car implements Serializable {
    private final String license;
    private final LocalDate boughtOn;

    @GeneratePojoBuilder
    protected Car(final String license, final LocalDate boughtOn) {
        this.license = license;
        this.boughtOn = boughtOn;
    }

    public String getLicense() {
        return license;
    }

    public LocalDate getBoughtOn() {
        return boughtOn;
    }

Next, and this is probably the most important step… run mvn clean compile to generate the builder classes. This allows the IDE to help us with code completion when using the builders.

Now, we can create the Person object like this:-

final Person person = new PersonBuilder()
        .withCars(ImmutableList.of(
                new CarBuilder()
                        .withBoughtOn(new LocalDate(2015, 1, 1))
                        .withLicense("123")
                        .build()))
        .withName("Mike")
        .build();

If you are running the program directly from IntelliJ, we need to ensure the program is compiled using Maven. To do this, edit the configuration for the runner class:-

Remove the default Make task:-

Add Maven goals:-

Enter clean and compile:-

Now, when IntelliJ runs the program, the program will be compiled using Maven to ensure no compilation errors on the builder classes during execution.