Expand Authors Top

If you have a few years of experience in the Java ecosystem and you’d like to share that with the community, have a look at our Contribution Guidelines.

Expanded Audience – Frontegg – Security (partner)
announcement - icon User management is very complex, when implemented properly. No surprise here.

Not having to roll all of that out manually, but instead integrating a mature, fully-fledged solution - yeah, that makes a lot of sense.
That's basically what Frontegg is - User Management for your application. It's focused on making your app scalable, secure and enjoyable for your users.
From signup to authentication, it supports simple scenarios all the way to complex and custom application logic.

Have a look:

>> Elegant User Management, Tailor-made for B2B SaaS

November Discount Launch 2022 – Top
We’re finally running a Black Friday launch. All Courses are 30% off until end-of-day today:


NPI – Lightrun – Spring (partner)

We rely on other people’s code in our own work. Every day. It might be the language you’re writing in, the framework you’re building on, or some esoteric piece of software that does one thing so well you never found the need to implement it yourself.

The problem is, of course, when things fall apart in production - debugging the implementation of a 3rd party library you have no intimate knowledge of is, to say the least, tricky. It’s difficult to understand what talks to what and, specifically, which part of the underlying library is at fault.

Lightrun is a new kind of debugger.

It's one geared specifically towards real-life production environments. Using Lightrun, you can drill down into running applications, including 3rd party dependencies, with real-time logs, snapshots, and metrics. No hotfixes, redeployments, or restarts required.

Learn more in this quick, 5-minute Lightrun tutorial:

>> The Essential List of Spring Boot Annotations and Their Use Cases

1. Introduction

In this article, we'll look at how we can implement the strategy design pattern in Java 8.

First, we'll give an overview of the pattern, and explain how it's been traditionally implemented in older versions of Java.

Next, we'll try out the pattern again, only this time with Java 8 lambdas, reducing the verbosity of our code.

2. Strategy Pattern

Essentially, the strategy pattern allows us to change the behavior of an algorithm at runtime.

Typically, we would start with an interface which is used to apply an algorithm, and then implement it multiple times for each possible algorithm.

Let's say we have a requirement to apply different types of discounts to a purchase, based on whether it's a Christmas, Easter or New Year. First, let's create a Discounter interface which will be implemented by each of our strategies:

public interface Discounter {
    BigDecimal applyDiscount(BigDecimal amount);

Then let's say we want to apply a 50% discount at Easter and a 10% discount at Christmas. Let's implement our interface for each of these strategies:

public static class EasterDiscounter implements Discounter {
    public BigDecimal applyDiscount(final BigDecimal amount) {
        return amount.multiply(BigDecimal.valueOf(0.5));

public static class ChristmasDiscounter implements Discounter {
   public BigDecimal applyDiscount(final BigDecimal amount) {
       return amount.multiply(BigDecimal.valueOf(0.9));

Finally, let's try a strategy in a test:

Discounter easterDiscounter = new EasterDiscounter();

BigDecimal discountedValue = easterDiscounter


This works quite well, but the problem is it can be a little bit of a pain to have to create a concrete class for each strategy. The alternative would be to use anonymous inner types, but that's still quite verbose and not much handier than the previous solution:

Discounter easterDiscounter = new Discounter() {
    public BigDecimal applyDiscount(final BigDecimal amount) {
        return amount.multiply(BigDecimal.valueOf(0.5));

3. Leveraging Java 8

Since Java 8 has been released, the introduction of lambdas has made anonymous inner types more or less redundant. That means creating strategies in line is now a lot cleaner and easier.

Furthermore, the declarative style of functional programming lets us implement patterns that were not possible before.

3.1. Reducing Code Verbosity

Let's try creating an inline EasterDiscounter, only this time using a lambda expression:

Discounter easterDiscounter = amount -> amount.multiply(BigDecimal.valueOf(0.5));

As we can see, our code is now a lot cleaner and more maintainable, achieving the same as before but in a single line. Essentially, a lambda can be seen as a replacement for an anonymous inner type.

This advantage becomes more apparent when we want to declare even more Discounters in line:

List<Discounter> discounters = newArrayList(
  amount -> amount.multiply(BigDecimal.valueOf(0.9)),
  amount -> amount.multiply(BigDecimal.valueOf(0.8)),
  amount -> amount.multiply(BigDecimal.valueOf(0.5))

When we want to define lots of Discounters, we can declare them statically all in one place. Java 8 even lets us define static methods in interfaces if we want to.

So instead of choosing between concrete classes or anonymous inner types, let's try creating lambdas all in a single class:

public interface Discounter {
    BigDecimal applyDiscount(BigDecimal amount);

    static Discounter christmasDiscounter() {
        return amount -> amount.multiply(BigDecimal.valueOf(0.9));

    static Discounter newYearDiscounter() {
        return amount -> amount.multiply(BigDecimal.valueOf(0.8));

    static Discounter easterDiscounter() {
        return amount -> amount.multiply(BigDecimal.valueOf(0.5));

As we can see, we are achieving a lot in a not very much code.

3.2. Leveraging Function Composition

Let's modify our Discounter interface so it extends the UnaryOperator interface, and then add a combine() method:

public interface Discounter extends UnaryOperator<BigDecimal> {
    default Discounter combine(Discounter after) {
        return value -> after.apply(this.apply(value));

Essentially, we are refactoring our Discounter and leveraging a fact that applying a discount is a function that converts a BigDecimal instance into another BigDecimal instance, allowing us to access predefined methods. As the UnaryOperator comes with an apply() method, we can just replace applyDiscount with it.

The combine() method is just an abstraction around applying one Discounter to the results of this. It uses the built-in functional apply() in order to achieve this.

Now, Let's try applying multiple Discounters cumulatively to an amount. We will do this by using the functional reduce() and our combine():

Discounter combinedDiscounter = discounters
  .reduce(v -> v, Discounter::combine);


Pay special attention to the first reduce argument. When no discounts provided, we need to return the unchanged value. This can be achieved by providing an identity function as the default discounter.

This is a useful and less verbose alternative to performing a standard iteration. If we consider the methods we are getting out of the box for functional composition, it also gives us a lot more functionality for free.

4. Conclusion

In this article, we've explained the strategy pattern, and also demonstrated how we can use lambda expressions to implement it in a way which is less verbose.

The implementation of these examples can be found over on GitHub. This is a Maven based project, so should be easy to run as is.

November Discount Launch 2022 – Bottom
We’re finally running a Black Friday launch. All Courses are 30% off until end-of-day today:


Generic footer banner
Comments are closed on this article!