Skip to main content

The GDS Way and its content is intended for internal use by the GDS and CO CDIO communities.

Java style guide

The purpose of this style guide is to provide some conventions for working on Java code within GDS.

The Google Java style guide is a good starting point. The old Sun/Oracle Java style guide is still largely relevant but it has not been updated since 1999 and does not reflect recent changes to the language.

The more far-reaching Java for Small Teams ebook can be read online at no cost. While not free, Effective Java by Joshua Bloch is also an excellent resource. The GDS Library has physical copies of the book or you can buy a copy using the Learning and Development budget (see the books page on the wiki).

While the above resources are good guides, they may conflict with either each other or your team’s established practices. We favour consistency across our code, so make sure that you have the agreement of your team when considering using a new or different method or paradigm to those currently in use.

We generally use IntelliJ IDEA within GDS. Using it consistently helps when pairing and mob programming. GDS is not able to purchase licences of the commercial editions of IntelliJ IDEA.

Code formatting

Variable and field names should match the names of their types (for example, an InputStream could be named inputStream or is), or have descriptive names reflecting the context of their use (for example, a Set<String> where each String is a username could be named usernames).

Always use curly braces around the body of an if, else, for, do or while statement, even if it’s only a single line. Follow the Kernighan and Ritchie (K&R) ‘Egyptian brackets’ style where there is no line break before the opening brace. See the braces section of the Google Java style guide for more details.

Use the GDS Way EditorConfig file, which has settings for things like code indentation. Place a copy of this file named .editorconfig in the root of your project to have IntelliJ IDEA and some other editors automatically apply the settings. If your editor does not support EditorConfig, manually configure its settings to match.

Some Java teams within GDS have had success using the Spotless auto-formatter. As a somewhat opinionated code formatter, it may want to make lots of changes if added to an existing project, so you may wish to configure it to match your existing conventions. For a new project, it’s probably easiest to use its default settings.

Dependency injection (DI)

Consider whether a dependency injection framework is appropriate for your project before using it. Some programmes use the Guice dependency injection framework.

Imports

You should not use any wildcard imports. Wildcard imports can cause existing code to break if a new type is added to a package with the same name as a type in another package. This infamously happened with Java SE 1.2, which introduced java.util.List when there was already java.awt.List, breaking any classes that used List and imported both java.util.* and java.awt.*.

You can configure IntelliJ to explicitly import all classes and static methods in Preferences → Editor → Code Style → Java → Imports with “Class count to use import with *” and “Names count to use static import with *” both set to a very high number, for example, 1000.

Static imports should be avoided in most cases because the names of methods and constants often do not make sense without the context of the type they’re from.

Static imports are appropriate in some cases. Tests often read more fluently when assertion and matcher methods, which are well known and widely understood, are statically imported. For example, compare:

Assert.assertThat(actual, Is.is(expected));

… to:

assertThat(actual, is(expected));

Similarly, Math.max(…), Math.PI and ChronoUnit.DAYS could be statically imported because their names make sense on their own. However, LocalDate.of(…) should not be statically imported because the type it comes from is crucial for comprehension.

The IntelliJ ‘Optimize Imports’ command sorts imports and removes any that are unused. Before committing some changes, you can select all the classes in the modified files pane and then use this command to fix the imports for just the classes you modified.

Optionals

If a getter may return null, you should usually return an Optional instead.

Optional is intended to only to represent the absence of a result: do not use it for fields or method parameters. Java language architect Brian Goetz posted a StackOverflow answer explaining the intent of Optional with further reasoning.

You almost never need to use the isPresent() method on an Optional. Use ifPresent(…), ifPresentOrElse(…), map(…) or flatMap(…) instead. See DZone Java Zone’s article Optional isPresent() Is Bad for You for more details.

Optionals work best when used in a functional style, which can take time to learn. The DZone Java Zone article 26 Reasons Why Using Optional Correctly Is Not Optional has some guidance.

Local variable type inference (the var keyword)

You can use var in Java 10 onwards with local variables to have the compiler infer the type. This is especially good when invoking a constructor:

// Java 9
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

// Java 10+
var outputStream = new ByteArrayOutputStream();

It can also remove duplication where the class name and the variable name are the same:

// Java 9
CheckResponse checkResponse = service.getCheckResponse();

// Java 10+
var checkResponse = service.getCheckResponse();

Be mindful that var hides the type of the variable. If the variable name makes the type obvious, this usually isn’t a problem. But if it is not clear from either the variable name or the right-hand side of the assignment, it might be better to explicitly write the type.

The OpenJDK project has some style guidelines for local variable type inference. Oracle’s introduction to local variable type inference also contains some recommendations.

Prefer functionality in the Java standard library

Where possible, use functionality from the Java standard library rather than external libraries.

Keep in mind that improvements to the Java standard library mean that some external libraries which were popular in the past now add less value. For example, while Joda-Time was significantly better than the date and time libraries built into older Java versions, the java.time package introduced in Java 8 renders it redundant. Similarly, Google’s Guava is very useful (and recommended) but the unmodifiable collections built in to Java 9 largely remove the need for Guava’s immutable collections.

When using external libraries, favour those which complement the Java standard library. For example, be wary of any library which introduces a new type that replicates the functionality of an existing type in the Java standard library without implementing the same interfaces (for example, a list type which does not implement java.util.List).

Comments

Agree with your team to what extent you permit comments in your code. It’s often possible to make code more readable so it doesn’t need comments.

Testing

Use JUnit for unit tests. It can also be used for many integration tests.

The latest JUnit 5 is not a drop-in replacement for JUnit 4. The JUnit Vintage test engine makes it possible to run JUnit 4 tests within JUnit 5, though not all JUnit 4 features are supported. If you use lots of JUnit 4 rules and alternative runners, or rely on other testing libraries that integrate with JUnit 4, you will have to make more changes. Teams within GDS have found it typically takes a few days to upgrade a project from JUnit 4 to JUnit 5. JUnit 4 continues to be maintained.

For new projects, use JUnit 5 unless you have a reason not to.

Use Mockito for mocking. If you’re still using Mockito 2, upgrading to Mockito 3 should be straightforward because there are no breaking changes.

Code checking

We encourage the use of static analysis. Static analysis tools for Java include SonarQube, Codacy, FindBugs and CheckStyle. However, be aware that such tools can detect an overwhelming number of problems if applied to an existing project, which tends to result in their checks being ignored.

If possible, configure your static analysis tools using configuration files and keep these in your project repositories. This makes your settings more portable and makes it easier to perform the same checks in different places (for example, in a cloud service and on your own computer).

Some cloud-based static analysis tools, including Codacy, can work directly with GitHub repositories. Only grant a tool access to your repositories if it has been approved by an information assurance review.

Try to minimise compiler warnings. If you cannot remove a warning, use an appropriate annotation to suppress it, preferably with a comment explaining why. For example:

public void frobulateFoos() {
    LOGGER.info("Frobulating foos");

    // LibFoo returns a raw list but every element is always a Foo
    @SuppressWarnings("unchecked")
    List<Foo> foos = (List<Foo>) fooService.getFoos();

    foos.forEach(foo -> foo.frobulate());
}

Dependencies

Try to keep up to date with the latest versions of your external dependencies. Older dependencies often contain security vulnerabilities. If you wait to upgrade your dependencies, you may find you have to make large version jumps to lots of dependencies at once, which can be painful. Frequent, smaller updates are almost always preferable.

Dependabot (which is part of GitHub) can automatically open pull requests to upgrade your libraries and other dependencies. Be aware that not all dependency upgrades are backwards compatible. Major version upgrades are more likely to cause problems than minor upgrades. Dependabot provides compatibility scores and links to release notes, which can help you make an informed decision. Don’t merge a dependency upgrade unless it passes your automated tests.

Dependabot makes assumptions about version numbers that not all dependencies follow. This can cause it to open pull requests that update to beta versions, release candidates or even alternative variants of the dependency. Always have a human sense-check each Dependabot pull request before merging.

Build tools

You should use either Gradle or Maven as the build tool. Use recent versions if you can.

Web frameworks

We use Dropwizard as our web framework of choice.

Dropwizard 2.0 was released at the end of 2019. There is a guide for migrating from Dropwizard 1.3.x to 2.0.x. Teams within GDS have found the upgrade straightforward.

Dropwizard has built-in support for validating requests with Hibernate Validator. Use Dropwizard’s validation in preference to rolling your own except in cases where Dropwizard’s built-in functionality cannot meet your validation requirements.

If you are sending logs to a service that requires them in a specific format, you may find our dropwizard-logstash logging extension useful.

JDK

The Oracle JDK is no longer free for production use. OpenJDK remains free to use (under the GPLv2 with the Classpath Exception) but Oracle only provide general-availability OpenJDK builds for the latest release.

The AdoptOpenJDK project (backed by big names including IBM and Microsoft) provides fully open-source pre-built OpenJDK binaries. For LTS releases (such as Java 8 and Java 11), AdoptOpenJDK have committed to releasing free updates for several years.

In addition, AdoptOpenJDK has benefits such as being available in package repositories, having friendly installers for desktop use, and offering ready-made Docker images containing OpenJDK.

Publishing artifacts

We recommend publishing artifacts (for which the source is already public) to Maven Central.

You should not use Maven Central to publish artifacts for which the source is closed or contains other proprietary assets, as it is a public repository with anonymous access.

Claiming group ids

You will need to claim one or more group ids in order to publish an artifact to Maven Central. The central repository is run and operated by Sonatype, and you will need to register for an account on their JIRA instance to request control of a group id.

The credentials used to create this account will be used during the publishing process, and should be stored safely and in accordance with any programme-specific guidance.

You should follow Sonatype’s guidance on registering for and claiming a group id such as uk.gov.example. It is likely that you will be asked to prove your identity as a government actor, to prevent malicious parties publishing artifacts and claiming that they have been issued by the UK government.

Signing artifacts

Sonatype requires that artifacts published to their repository have been signed with a published GPG key. Each programme that wants to publish artifacts should create their own GPG key, publish it to a public keyserver, then store the private key safely and in accordance with any programme-specific guidance.

The key and its associated passphrase will be used during the publishing process. This will require making it available safely to the task runner, for example Concourse, that is performing the deployment.

We recommend additionally publishing the public keys elsewhere, for example as a public GitHub repository, so that a third-party user knows an artifact is signed by a key that we have declared we use for signing.

Sonatype publishes build-tool-specific guidance for publishing and releasing artifacts on Maven Central. GDS primarily uses Maven and Gradle as their build tools.

This page was last reviewed on 20 January 2021. It needs to be reviewed again on 20 July 2021 by the page owner #gds-way .
This page was set to be reviewed before 20 July 2021 by the page owner #gds-way. This might mean the content is out of date.