Cologne. Anno Domini 1470. For over two hundred years German craftsmen have been working on the cathedral close the Rhine. At the very moment, master Tilman is busy decorating one of the pillars in the left center of the nave. He has done this profession for his entire live. His hands carve a figure from a grey stone. First the baby Jesus emerges. Then a head, a body and finally the feet of a man come into view. It is the saint Christopher. According to legend, together with the divine child this saint carries the burden of the entire world. It is a marvel to watch the skilled worker chisel a man from rock. And yet, if you watch him closely, you start to wonder if he really has to use his old tools. Wouldn’t he do his job even better with new shiny gear? Does the veteran artisan really know all the tricks, or could even he learn something new?

In our line of work, there is always the struggle between treading the known path versus stepping into the unknown. For some the new entails wonder and exploration, for others it’s yet another nightmare to wade through. From my experience, this is especially true when talking about using functions in Java. If you haven’t used them yourself, or aren’t even familiar with the concept at all, this blog post is for you.

If there were dreams to sell, what would you buy?

Currently, the Cologne Cathedral sells souvenirs, books and gifts at the Domshop. Imagine this shop uses a Java application to manage all transactions. Though simplified[1], it could contain a class with some REST endpoints:

import com.cologne.cathedral.Repository;
import com.cologne.cathedral.model.entity.Book;
import com.cologne.cathedral.model.entity.Invoice;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class ShopEndpoints {
    private final Repository<Book> bookRepo;
    private final Repository<Invoice> invoiceRepo;

    @PostMapping("/books/sell/{id}")
    private Book sellBook(@PathVariable final long id, @RequestParam final long userId) {
        final var book = bookRepo.findById(id);
        book.wrapInPaper();
        book.setOwner(userId);
        bookRepo.save(book);

        return book;
    }

    @PostMapping("/invoices/complete/{id}")
    private Invoice completeInvoice(@PathVariable final long id) {
        final var invoice = invoiceRepo.findById(id);
        invoice.complete();
        invoiceRepo.save(invoice);

        return invoice;
    }
 }

Duplication is a sin

If you observe above endpoints methods, one could see a pattern of completion shared between the two. Despite the fact this code is rather small, we could give it more intent by introducing a CompletableCommand interface:

public interface CompletableCommand<T> {
    T finish();
}

As both methods do a database save action when finalizing, let’s also make an abstract class to make the code more DRY:

@RequiredArgsConstructor(access = PROTECTED)
public abstract class DbCompletableCommand<T> implements CompletableCommand<T> {
    private final Repository<T> repository;
    private final T t;

    protected abstract void doFinish(T t);

    @Override
    public T finish() {
        doFinish(t);
        return repository.save(t);
    }
}

Now the basic structure is complete, we can make two implementations. One for the book:

public class CompleteBook extends DbCompletableCommand<Book> {
    private final long userId;

    public CompleteBook(final Repository<Book> repository, final Book book, final long userId) {
        super(repository, book);
        this.userId = userId;
    }

    @Override
    public void doFinish(final Book book) {
        book.wrapInPaper();
        book.setOwner(userId);
    }
}

and another one for the invoice:

public class CompleteInvoice extends DbCompletableCommand<Invoice> {
    public CompleteInvoice(final Repository<Invoice> repository, final Invoice invoice) {
        super(repository, invoice);
    }

    @Override
    public void doFinish(final Invoice invoice) {
        invoice.complete();
    }
}

That’s a lot of extra code, but the endpoint methods themselves are both shorter and clearer:

@PostMapping("/books/sell/{id}")
private Book sellBook(@PathVariable final long id, @RequestParam final long userId) {
    final var book = bookRepo.findById(id);
    return new CompleteBook(bookRepo, book, userId).finish();
}

@PostMapping("/invoices/complete/{id}")
private Invoice completeInvoice(@PathVariable final long id) {
    final var invoice = invoiceRepo.findById(id);
    return new CompleteInvoice(invoiceRepo, invoice).finish();
}

There are other ways, other paths that we might take

Solving problems like the one above with an object-oriented approach is a common thing to do. Big chance you’ve built code like this a thousand times. But since Java 8 there is another fine mechanism you could use; every interface with one single abstract method is considered a functional interface automatically. To give your interface compile time safety, you can add a @FunctionalInterface annotation to guard for addition of multiple non-overriding abstract methods.

As it turns out, the CompletableCommand interface applies to this rule. Having this knowledge, we could skip the whole abstract-and-implementation classes part. First we define a finisher helper method. The functional interface and the repository are the required input arguments. The implementation is very close to the 'finish' method in the abstract DbCompletableCommand class:

private static <T> void finish(final CompletableCommand<T> command, final Repository<T> repository) {
    final var t = command.finish();
    return repository.save(t);
}

By creating an anonymous class on the fly, we can just call this method. Let’s use the 'sellBooks' method as example:

@PostMapping("/books/sell/{id}")
private Book sellBook(@PathVariable final long id, @RequestParam final long userId) {
    final var book = bookRepo.findById(id);
    return finish(new CompletableCommand<Book>() {
        @Override
        public Book finish() {
            book.wrapInPaper();
            book.setOwner(userId);
            return book;
        }
    }, bookRepo);
}

That’s it. Amusing fact, the code above could have been written ages ago[2]. Most of us don’t write this kind of code, because it is super verbose. But here comes the fun part, functional interfaces can be replaced by either lambda’s or method references. And that does change the world:

@PostMapping("/books/sell/{id}")
private Book sellBook(@PathVariable final long id, @RequestParam final long userId) {
    final var book = bookRepo.findById(id);
    return finish(() -> {
        book.wrapInPaper();
        book.setOwner(userId);
        return book;
    }, bookRepo);
}

The surprise is half the battle

You might be wondering, this blog would address functions right? What about Java’s functional interfaces? Actually there is much less to talk about than one might think. The functional concept behind the CompletableCommand interface does also apply to all the native functional interfaces. The three most common ones are listed in the table below:

Name Behaviour Signature Example

Consumer

Provide an object, do something, do not return anything

T ⇒ void

book → book.tearOffPage(41)

Supplier

Provide nothing, do something to return an object

void ⇒ T

() → Files.lines(Paths.get("/invoice-23812.txt"))

Function

Provide an object, do something, return an object

T ⇒ R

book → book.getContentOfPage(18)

Now comes the gist, our very own CompletableCommand interface is nearly identical to the Supplier interface. In fact, we can just remove it and improve the finisher helper method by using the native functional interface over our own defined interface:

private static <T> void finish(final Supplier<T> command, final Repository<T> repository) {
    final var t = command.get();
    return repository.save(t);
}

And thus it happened, all our additional object-oriented is replaced by one simple helper method!

It is deeply satisfying to shape something with your hands

Old tools, new tools. In the hands of the master they are all the same. Whether you are a craftsmen creating art or a Java programmer creating beauty. In doesn’t matter as long as no device is foreign to you. Master Tilman’s work lasted centuries. What about yours? Will your functions echo eternity?


1. For convenience’ sake, I use Lombok, some annotations from the Spring Framework and some undefined entities and repository.In a production ready application you would at least separate the logic to another service.
2. Anonymous classes were introduced in Java 1.1.
shadow-left