---
title: One year using Java 8 in production
url: https://blog.octoperf.com/one-year-using-java-8-in-production/
date: '2015-08-24'
authors:
- jloisel
categories:
- Development
tags:
- Java
- Lombok
- Couchbase
- Github
- JSON
- Spring
description: Experience feedback on how we use Java 8 for over one year on our servers.
---

# One year using Java 8 in production

We've been using Java8 since the beginning of our startup. Java8 was released on [18 March 2014](https://en.wikipedia.org/wiki/Java_version_history#Java_SE_8_.28March_18.2C_2014.29). We started using Java in September 2014, when AWS made it available on [ElasticBeanStalk](https://aws.amazon.com/fr/elasticbeanstalk/). At this time, Java 8 was available for almost 6 months. It was mature enough to give it a try.

Frameworks compatible with Java 8 like [Spring 4.0](https://spring.io/) started to be available. Java 8 is a huge step forward compared to Java 7 :

- **Lambdas**: concise way to write *Function* and *Predicate*, a step toward functional programming,
- **New Apis**: Optional, Streams, Dates,
- **Default Methods**: define default method behavior in interfaces.

One year later, what did we really use? How did we use it? These are some of the questions we're going to answer.

## What about Guava

Guava is a [Google Utility API](https://github.com/google/guava). Before Java 8, we were widely using Guava's features like *Predicate*, *Function*, *Optional* and more. We feel like Java 8 brings almost everything what Guava is providing. **We gradually replace Guava features by their Java 8 equivalent**.

The question whether to keep Guava or not is a good question. Java 8 picks a great number of features from Guava, and it's not a surprise. Guava is one of the most famous Java API, just behind [JUnit](https://junit.org/).


**Is Guava still useful?** *Guava* should still be in your classpath because it provides extremely useful APIs like:

- **Immutable collections**: we use them almost every time a collection is required. Immutability is vital to maintain your sanity,
- **EventBus**: [publish / subscribe style communication](https://github.com/google/guava/wiki/EventBusExplained) between components that does not require to know each other,
- **Caches**: Argh! We use them rarely because [cache invalidation](https://martinfowler.com/bliki/TwoHardThings.html) is the second most difficult thing in programming.

Maybe the day will come when all Guava utilities are inside the JDK.

## Lambdas

Lambdas are the greatest improvement in Java 8 so far for us. It makes code more concise, while still being understandable. Lambdas have definetely changed the way we are programming: the [functional programming](https://en.wikipedia.org/wiki/FP_(programming_language)) paradigm is a growing trend we tend to follow.

### Java 7 vs Java 8

```java
// Java 7
final Predicate<String> isEmpty = new Predicate<>() {
  @Override
  public void test(final String input) {
    return Strings.isNullOrEmpty(input);
  }
};

// Java 8
final Predicate<String> isEmpty = Strings::isNullOrEmpty;
```

*Predicate checking whether a string is null or empty.*

**Java 7 is verbose** whereas **Java 8 is concise**. We're not especially advocates of concise code: we think the [Scala language](https://www.scala-lang.org/) goes too far in this way. Lambdas are a good balance between the unnecessary Java 7 verbosity and Scala's ultimate conciseness.

### RxJava and lambdas

[RxJava](https://github.com/ReactiveX/RxJava) profits from lamdbas too. *RxJava* was almost unusable with Java 7 due to the verbosity of the syntax. Now our code is just flawless.

```java
@Override
public Observable<T> save(final Iterable<T> entities) {
return Observable.from(entities)
    .map(serializer)
    .flatMap(bucket::upsert)
    .map(deserializer)
    .timeout(timeout, unit);
}
```

*Needless to say we wouldn't have used RxJava with Java 7 due to extra verbosity.*

*RxJava* still lacks comprehensiveness when it comes to debugging. The asynchronous nature of *Observable* makes it painful to debug when something goes wrong. We use *RxJava* sparingly, mainly because of the [Couchbase Java SDK](https://github.com/couchbase/couchbase-java-client). **Code maintainability profits from better code readability**. Although, **lambdas are difficult to understand for developers not used to functional programming.** We've been developing with functional programming in mind for years, since we discovered Google Guava.

### When we use lambdas

**We are using Lambda expressions extensively**. We try to avoid overusing them: sometimes code with lambdas can be less readable than plain old code. When the execution within a lambda fails, it can show such kind of stacktraces:

```sh
at LmbdaMain$$Lambda$1/821270929.apply(Unknown Source)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.LongPipeline.reduce(LongPipeline.java:438)
at java.util.stream.LongPipeline.sum(LongPipeline.java:396)
at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526)
at LmbdaMain.main(LmbdaMain.java:39)
```

*Stacktraces with exceptions thrown by lambda functions are cumbersome.*

**Lambdas are not well-suited for simple operations**. If the plain code using for loops or if conditions looks simpler, resist the temptation to convert it to lambda expressions. Developers are prone to overuse when they find a new [golden hammer](https://en.wikipedia.org/wiki/Law_of_the_instrument). Also, we **avoid chaining more than 6 operations**. The sanity gain provided by the concise code is lost if it's being used to increase the local code complexity. We try to follow the [K.I.S.S. principle](https://en.wikipedia.org/wiki/KISS_principle): Keep It Simple And Stupid.

## Optional

We were already extensively using Guava's Optional. **Optional is the perfect usage to explicitly indicate when a method does not return an object**.

```java
public interface Repository<T> {
  Optional<T> find(String id);
}
```

*A typical database repository interface.*

Let's explore the alternatives to *Optional* to better understand why using it.

### Avoiding Null

We're advocate of **never have NULL objects**. Null is not obvious, Null is not safe. Null requires cumbersome code like this:

```java
if(instance != null) {
  doSomething();
} else {
  // don't know what to do
}
```

What to do when the object is **null**? Does it mean the method failed? Is it a special case? With **Optional** things are becoming clearer. [Using and avoiding null](https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained) explains this very well.

### Example

Let's take the following example code.

```java
public interface OutputStrategy {
  void output(String id);
}
```

*The output strategy interface defines the contract.*

```java
public class SystemOutputStrategy implements OutputStrategy {
  public void output(String id) {
    System.out.println(id);
  }
}
```

*Output the id in the console.*

```java
public interface OutputStrategyService {
  OutputStrategy get();
}
```

The *OutputStrategyService* returns an OutputStrategy instance. Its behavior depends on the pattern used below.

### Null Pattern

In this case, the **OutputStrategy can be NULL**. Caller must check the nullity each time the service is called.

```java
// Code using the service with null object pattern
final OutputStrategy s = service.get();
if(s != null) {
  s.output("test"); 
} else {
  // What to do? throw? silently go further?
}
```

*Using Null Pattern.*

This is particularly cumbersome and inefficient. **It requires a conditional Null check every time the strategy is queried**. The developer must read the code of each called service to check whether it may return NULL.

### Null Object Pattern

Another alternative to **NULL** is to use the [Null Object Pattern](https://en.wikipedia.org/wiki/Null_Object_pattern). The following example shows a very basic usage of the *Null Object Pattern*.

The null object pattern is defined as following.

```java
public class NullOutputStrategy implements OutputStrategy {
  public void output(String id) {
    // Do nothing
  }
}
```

*Noop output.*

The latest strategy does nothing. The following code does not require a null check because the service always returns an *OutputStrategy* instance.

```java
// Code using the service with null object pattern
final OutputStrategy s = service.get();
s.output("test");
```

*Using Null Object Pattern.*

The call to the output method does nothing. No extra check is required.

### Optional Pattern

The code below shows how it would have been using an Optional.

```java
public interface OutputStrategyService {
  Optional<OutputStrategy> get();
}

// Code using the service with optional pattern
final Optional<OutputStrategy> optional = service.get();
optional.ifPresent(s -> s.output("test"));
```

*Using Optional Pattern.*

*Optional* explicitly tells the developer that the returned instance may not be available. **The code doesn't compile if you don't manage the returned Optional properly**. Developers save time: they don't need to read the service implementation to check for nullity.

### Guava Optional vs Java Optional

There are some differences between Guava's and Java's Optional:

```java
public final class Optional {
	static Optional<T> absent();
	static Optional<T> fromNullable(instance);
	boolean isPresent();
	Set<T> asSet();
}
```

*Guava Optional main methods.*

The difference with Java's Optional is negligible, some methods are renamed, and *asSet()* method is missing

```java
public final class Optional {
	static Optional<T> empty();
	static Optional<T> ofNullable(instance);
	boolean isPresent();
}
```

*Java Optional main methods.*

Migrating from Guava's to Java's Optional has been almost flawless. We use [Jackson](https://github.com/FasterXML/jackson) for object to Json serialization and it just worked fine. Although, we experienced issues with some externals APIs depending on Guava. **We have some classes cluttered with both Java's and Guava's Optional**, but it's only marginally happening.

## Lombok

### What is Lombok

[Lombok](https://projectlombok.org/)  is a life saver. It uses annotation processing to generate *toString()*, *hashcode()*, constructors. It generates boilerplate code for you. **Lombok and Java 8 are working together without any issue**. Lombok requires to be [installed as Java Agent to work with Eclipse](https://projectlombok.org/download.html). Intellij has its [own Lombok Plugin](https://github.com/mplushnikov/lombok-intellij-plugin), but it doesn't support all the annotations.

```java
@Value
@Builder
public class Person {
  @NonNull
  String firstname;
  String lastname;
  int age;
}
```

*Generates an immutable Person which can be instanciated using a [Builder](https://en.wikipedia.org/wiki/Builder_pattern).*

### Less code

I had used Lombok before on a small project (45K lines of code). Lombok allowed us to remove about **7K lines of code (15% of the code)**. Not only does it save lines of code to maintain, **it also prevents bugs**. We were previously generating *equals* and *hashcode* using Eclipse built-in feature. It was generating code that looks like:

```java
@Override
public boolean equals(Object obj)
{
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    CompanyRole other = (CompanyRole) obj;
    if (name == null)
    {
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))
        return false;
    return true;
}
```

*Ugly Eclipse generated equals method.*

What happens when you add a new field to the object? Nothing! You have to manually remove and regenerate the equals and hashcode methods. **With Lombok, this is automatic**. You see now how bad it can get with manually generated code!

### Lombok and Spring 

**Spring works flawlessly with lombok too**. We generate *autowired immutable* Controller constructors.

```java
@RestController
@RequestMapping("/bench/report")
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@AllArgsConstructor(access = AccessLevel.PACKAGE, onConstructor = @__(@Autowired))
class BenchReportController extends AbstractCrudServiceController<BenchReport> {
  @NonNull
  BenchReportCrudService benchReports;

  ...

}
```

*Immutable Controller with autowired Spring constructor, private final fields.*

### JUnit testing

Lombok generated *equals()*, *hashcode()* and constructors can be easily JUnit tested using [EqualsVerifier](https://github.com/jqno/equalsverifier) and [NullPointerTester](https://github.com/google/guava/blob/master/guava-testlib/src/com/google/common/testing/NullPointerTester.java) (Guava-testlib).

```java
public class BenchReportTest {

  @Test
  public void shouldPassEqualsVerifier() {
    EqualsVerifier.forClass(BenchReport.class).verify();
  }

  @Test
  public void shouldPassNullPointerTester() {
    new NullPointerTester().testConstructors(BenchReport.class, Visibility.PACKAGE);
  }
}
```

*JUnit testing the equals / hashcode contract, and null check preconditions on constructor.*

Beware that unit testing the constructor preconditions using *NullPointerTester* does get you 100% test coverage on the constructor generated by Lombok. You have to create the constructor manually as shown in the following section.

### Jackson Immutable objects

[Jackson](https://github.com/FasterXML/jackson) is a Json serializer / deserializer. *Lombok* can be paired with *Jackson* to easily generate immutable beans. You just need to create the constructor manually.

```java
@Value
@Builder
public class Person {
  @NonNull
  String firstname;
  String lastname;

  @JsonCreator
  Person(
    @JsonProperty("firstname") final String firstname,
    @JsonProperty("lastname") final String lastname) {
    this.firstname = checkNotNull(firstname);
    this.lastname = checkNotNull(lastname);
  }
}
```

*Jackson immutable object using Lombok.*

Getters, equals and hashcode, builder are generated automatically on the fly.

## Dates API

The new Java Dates API becomes very close to [Joda-Time](https://www.joda.org/joda-time/). We're still using Joda-time because the new Java Dates lacks some features that Joda have. We use the Java Dates API when Joda's features are not required. **Joda dates also serialize better using Jackson than Java 8 Dates**. We had some [issues](https://github.com/FasterXML/jackson-datatype-jsr310/issues?q=is%3Aissue+is%3Aclosed) with Jackson serialized beans using JDK8 dates.

## Streams

You can finally get ride of Guava's FluentIterable, Streams are here! Commons operations like *map*, *filter*, or *flatMap* make collections processing way easier. **We use streams** almost everywhere now, it saves a good amount of code. Like lambdas, **streams should be used wisely** to avoid code complexity build up. Straight code may be a better option sometimes.

### Parallel Streams

Parallel streams use a **static** *ForkJoinPool* which is configured by default to *System.availableProcessors()*. This can lead to competition between streams executing in different web context and create contention. Read [why parallel streams are bad](https://zeroturnaround.com/rebellabs/java-parallel-streams-are-bad-for-your-health/) to get a better insight about the issue.

## Function and Predicate

When you're used to Guava *Function* and *Predicate*, you'll be quickly used to Java ones. Some methods are renamed while the principle stays the same. *Predicate* and *Function* are mandatory when using *Streams*. We used to create reusable package protected implementations for testing purpose. **Now, we mostly create those using Lambdas**.

## Tomcat 8 on BeanStalk

**Tomcat 8 running on Java 8 has been successful for us so far**. We couldn't migrate to Java 8 until AWS made Tomcat 8 with Java 8 available on BeanStalk.

## Conclusion

Java 8 is a major release which brings features making Java development fun again. We feel like it's the best update Oracle (previously Sun) has made to Java in the last 10 years. If you haven't bumped to Java 8 yet, do it quickly. **We appreciate the productivity gain everyday**.

## Useful Links

- [Java 8 features](https://www.javacodegeeks.com/2014/05/java-8-features-tutorial.html),
- Curated list of the [best Java Frameworks](https://github.com/akullpp/awesome-java).
