Code Review Videos > Java > Java Guest Book Example [Beginner Spring Boot]

Java Guest Book Example [Beginner Spring Boot]

I’ve got an idea for a much larger scale Java project, but I want to work my way up from the bottom and make sure I’ve nailed the basics before diving in too deep.

For a long while I’ve wanted to do more Java posts on here, but I couldn’t figure out a way to structure them in ‘the right way’ (whatever that is), and it stopped me from writing.

Bad.

Much better to just write, and get stuff out there than worry about some imagined perfect structure that I can’t ever seem to find, and ultimately don’t write a thing.

So, here we go. Something basic.

What Will You Learn In This Post?

The main things covered are:

  • Project Structure: Separating the domain model from the Spring framework to maintain clean architecture. The use of directories and packages is laid out to help illustrate the typical directory structure I have seen used in the real world.
  • Spring Boot Integration: The post illustrates how to integrate Spring Boot with business logic, demonstrating the use of dependency injection, and the separation of concerns between the core logic and the framework.
  • Use of Annotations: The tutorial explains the use of various Spring annotations like @Service, @Primary, @Qualifier, and @Autowired to manage bean creation and dependency injection.
  • Data Handling: It covers data handling in Spring Boot, showing how to create endpoints using @GetMapping and @PostMapping annotations, and how to use @RequestBody to handle JSON payloads effectively.
  • Improving Code with Java Features: How newer Java features like records can simplify data transfer objects (DTOs), making the code cleaner and more concise.

The Example Project: A Guest Book

I remember doing a sample project a good few years back now, where the example was a “wallpaper site”, and when I did that the concept of desktop wallpapers was old fashioned, as everyone had moved on to mobile phones by that point. Desktops were something only old people used.

Continuing that theme, I figured why not do a classic Internet Guest Book as my basic example here. You remember guest books, don’t you? Back from when the Internet was still good? No? Oh well.

In case you were born after the turn of the millennium, let me explain the concept, as it really is super basic.

Back in the olden days of the Internet, you could go to a web page and there was often a Guest Book. It was a simple form with a name and message, and it would then post that to the server, where it would then be displayed – often immediately – for other site visitors to see. Just like the hand written guest books previously found in guest houses, which you would now be charged a writing fee for use, if using AirBnB, or similar. Yes, I know I’m very cynical.

Right, so the gist: basic web form.

Lot of words to get to that point.

And we won’t even be bothering with a front end of any kind, so perhaps an even better name for this would have been a ‘Rest Book’.

fozzy bear wakka wakka

Terrible.

To make it suitably Java-ish, I figured I would split out the ‘domain model’ from the implementation. That sounds incredibly grand, and on this sort of scale, dare I say, utterly pointless. It actually makes a lot of extra work. But the point here is to lay some ground rules, and understand the foundations of larger, more real-world Java apps.

The Domain Model

Java is verbose.

One such way I find it verbose is in the initial directory structure. I gain an extra layer just because I use .co.uk as part of the reverse domain naming, but even so, what’s one extra nest in something already 6 layers deep before we create a single file?

src
└── main
    └── java
        └── uk
            └── co
                └── a6software
                    └── guest_book
                        └── core
                            ├── model
                            │   ├── MessageImpl.java
                            │   └── Message.java
                            └── service
                                ├── MessageServiceImpl.java
                                └── MessageService.java

The core of this app is super simple, but that isn’t really what I am focusing on.

It’s the structure.

I know I want to use Spring Boot as the framework around this code. However, the code that runs the guest book should be completely independent / de-coupled from the framework.

Here are the two interfaces, for completeness. Any interesting parts of the implementation will be called out, but the rest can be seen on GitHub.

// src/main/java/uk/co/a6software/guest_book/core/model/Message.java

package uk.co.a6software.guest_book.core.model;

public interface Message {
    String getMessage();
}Code language: Java (java)

And:

// src/main/java/uk/co/a6software/guest_book/core/service/MessageService.java

package uk.co.a6software.guest_book.core.service;

import uk.co.a6software.guest_book.core.model.Message;

import java.util.List;

public interface MessageService {
    void postMessage(String user, Message message);

    List<Message> getMessages();
}Code language: Java (java)

Here’s the actual implementations:

// src/main/java/uk/co/a6software/guest_book/core/model/MessageImpl.java

package uk.co.a6software.guest_book.core.model;

public record MessageImpl(String message) implements Message {
    @Override
    public String getMessage() {
        return message;
    }
}Code language: Java (java)

And:

// src/main/java/uk/co/a6software/guest_book/core/service/MessageServiceImpl.java

package uk.co.a6software.guest_book.core.service;

import uk.co.a6software.guest_book.core.model.Message;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class MessageServiceImpl implements MessageService {
    private final List<Message> messages = new ArrayList<>();

    @Override
    public void postMessage(String user, Message message) {
        this.messages.add(message);
    }

    @Override
    public List<Message> getMessages() {
        return this.messages;
    }
}Code language: Java (java)

That is really it for the domain model. Super simple as I say. It could be simplified further, by removing the concept of a Message object, and simply relying on strings. However I intentionally made it slightly more complex, because why not?

The Infrastructure

The infrastructure layer is, in layman’s terms, all the stuff that connects Spring Boot (or, the framework) to my code (or, the business logic).

Here’s the directory structure after adding all this lot in:

src
└── main
    └── java
        └── uk
            └── co
                └── a6software
                    └── guest_book
                        ├── core
                        │   ├── model
                        │   │   ├── MessageImpl.java
                        │   │   └── Message.java
                        │   └── service
                        │       ├── MessageServiceImpl.java
                        │       └── MessageService.java
                        └── infrastructure
                            ├── config
                            │   └── AppConfig.java
                            ├── controller
                            │   └── MessageController.java
                            ├── dto
                            │   └── MessageRequest.java
                            ├── GuestBookApplication.java
                            └── service
                                └── SpringMessageService.java

In a way, it’s kinda wild that there is more code to connect everything than there is to actually do the work.

But of course, that’s skewed by the tiny size of the example.

There are some interesting things to cover here, the first of which is that there is no persistence of messages once the app is shut down.

I may get to that at some future point, or just skip it for this example. I guess that depends how the mood takes me. Whether this would be database connectivity or saving to the local file system, or some other idea, that would be an infrastructure layer concern.

Adding SpringMessageService

The first interesting file is SpringMessageService.java:

// src/main/java/uk/co/a6software/guest_book/infrastructure/service/SpringMessageService.java

package uk.co.a6software.guest_book.infrastructure.service;

import org.springframework.stereotype.Service;
import uk.co.a6software.guest_book.core.model.Message;
import uk.co.a6software.guest_book.core.service.MessageService;

import java.util.List;

@Service
public class SpringMessageService implements MessageService {
    private final MessageService messageService;

    public SpringMessageService(MessageService messageService) {
        this.messageService = messageService;
    }

    @Override
    public List<Message> getMessages() {
        return this.messageService.getMessages();
    }

    @Override
    public void postMessage(String user, Message message) {
        this.messageService.postMessage(user, message);
    }
}
Code language: Java (java)

This class makes use of Spring’s dependency injection to separate the business logic from the framework.

You can see how constructor injection is used (line 15) to pass in some implementation of the MessageService interface. I kinda like how Dot Net / C# tends to favour prefixing interfaces with an I, so rather IMessageService, but that’s not the Java way. It does make it more readable, in my opinion.

The SpringMessageService doesn’t actually do very much at all. When asked to getMessages or postMessage, it delegates the actual responsibility to another component.

A big selling point, to me at least, of doing this sort of thing is how this both better enables, and massively simplifies unit testing. That’s not something I’m doing here, but on other projects that use this same pattern, this is a major win.

The thing is, we now have two implementations of MessageService. We have this one, the SpringMessageService, and we have the one from our core, which is MessageServiceImpl. And this creates a problem:

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

   messageController defined in file [/home/chris/Development/guest-book/target/classes/uk/co/a6software/guest_book/infrastructure/controller/MessageController.class]
┌─────┐
|  springMessageService defined in file [/home/chris/Development/guest-book/target/classes/uk/co/a6software/guest_book/infrastructure/service/SpringMessageService.class]
└─────┘


Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.


Process finished with exit code 1Code language: plaintext (plaintext)

Of course I haven’t actually shown the Message Controller code just yet, but trust me, this is a problem.

I’d say this is actually a fairly nice error message, assuming you understand what it is trying to tell you. Equally I’d say that is the case with a lot of Java errors – sure, they are verbose as heck, but they do give a lot of clues, if you take the time read what they are saying. I may come back to hate myself for thinking that, but as of writing, that’s my current opinion.

What this means is that we must tell Spring which implementation of the interface we actually mean when we want to inject that interface.

The problem with this was that I simply couldn’t remember how to do this. I knew it was possible thanks to watching one of the videos on the Spring Academy website a few months back – that’s a completely free series of courses, by the way, well worth signing up for – but I couldn’t remember exactly how to set one to be preferred.

Multiple Ways To Wire A Bean

Of course, it turns out there are multiple ways to do this.

The first, and the one I stumbled upon after wracking my brain, was to use @Primary.

// src/main/java/uk/co/a6software/guest_book/infrastructure/service/SpringMessageService.java

package uk.co.a6software.guest_book.infrastructure.service;

// other imports ...
import org.springframework.context.annotation.Primary;

import java.util.List;

@Service
@Primary
public class SpringMessageService implements MessageService {
    private final MessageService messageService;
Code language: Java (java)

Once I’d found that, the docs reminded me of the other alternative, which is @Qualifier.

In this particular case, @Primary is almost certainly fine.

But out there in the real world, and indeed on some of the large Java projects I infrequently interact with, they make use of @Qualifier.

The reason, as best I can discern, is that figuring out the Primary bean is pretty tricky once your app gets above a certain size. That’s a threshold that’s not concrete, so explaining that in the docs might be difficult, but a Qualifier is more explicit, and practically, easier to search for.

With @Primary, there was a two line change. We needed the import, and to actually use the annotation. I guess that’s “save time now, pay for it later”.

With @Qualifier, there are several changes to make. The important thing is that at no point do I want to add ‘Spring stuff’ to the core code.

OK, so first change, annotating the SpringMessageService:

// src/main/java/uk/co/a6software/guest_book/infrastructure/service/SpringMessageService.java

package uk.co.a6software.guest_book.infrastructure.service;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import uk.co.a6software.guest_book.core.model.Message;
import uk.co.a6software.guest_book.core.service.MessageService;

import java.util.List;

@Service
@Qualifier("springMessageService")
public class SpringMessageService implements MessageService {

    private final MessageService messageService;

    public SpringMessageService(@Qualifier("coreMessageService") MessageService messageService) {
        this.messageService = messageService;
    }

   // ... no further changes
Code language: Java (java)

In order to make this work we need to import the annotation, then annotate the class. The name used for our qualifier can, I think, be anything.

I chose to use springMessageService – with the lower case s – because this is, as best I am aware, the convention that Spring uses in the naming that it comes up with for creating beans automatically. The generated bean name is camelCased based on the ClassName. I’m not 100% on this, but I believe that’s right.

Then, on line 18 I am injecting another instance of a class that implements the MessageService interface. This is a cause of confusion for Spring, as it does not know which of the available implementations I wanted.

Now I provide @Qualifier("coreMessageService") to help it understand what I mean. Verbose, but explicit.

Incidentally, whilst the qualifier names are “just strings”, it turns out Spring and IntelliJ can infer a bunch of things from this information, and it will know if you use a qualifier that doesn’t exist:

IntelliJ complains about wrong / missing qualifier Spring Boot
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-08-31T18:32:13.221+01:00 ERROR 252330 --- [guest-book] [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'messageController' defined in file [/home/chris/Development/guest-book/target/classes/uk/co/a6software/guest_book/infrastructure/controller/MessageController.class]: Unsatisfied dependency expressed through constructor parameter 0: No qualifying bean of type 'uk.co.a6software.guest_book.core.service.MessageService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier("springMe1ssageService")}Code language: plaintext (plaintext)

Also the application seems to build and run fine if I omit the explicit use of @Qualifier("springMessageService") on line 13 above. And equally I get the unhappy red text in IntelliJ if I use request SpringMessageService with uppercase. That’s pretty useful to know, but I still think I’d prefer to be explicit here and provide the named Qualifier.

Providing AppConfig

In my bid to decouple the business logic / core code from Spring, I am not wanting any Spring ‘stuff’ (annotations, etc) to be added to my core code.

This creates a small problem in that I do want Spring to manage my core code, and therefore it needs to know what things are available.

Therefore I need to provide some custom configuration code, where I declare Beans and such.

We already touched on this above, with the use of @Qualifier("coreMessageService"). That’s nice and all, but without the associated configuration below, it won’t work.

// src/main/java/uk/co/a6software/guest_book/infrastructure/config/AppConfig.java

package uk.co.a6software.guest_book.infrastructure.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import uk.co.a6software.guest_book.core.service.MessageService;
import uk.co.a6software.guest_book.core.service.MessageServiceImpl;

@Configuration
public class AppConfig {
    @Bean
    public MessageService coreMessageService() {
        return new MessageServiceImpl();
    }
}
Code language: Java (java)

Note the name of the method on line 13 above – coreMessageService.

Whatever that method name is set to becomes the string I must use in the Qualifier annotation.

Oh, and yeah, one thing I tend to routinely forget is to add the @Configuration annotation. You can do all the hard work in that file, get absolutely everything correct, but if you forget that nothing will work the way you expect. Fun times.

The Controller

I said back at the start we wouldn’t be creating any kind of front end.

Instead we will have two HTTP endpoints. One for creating new Messages, and another for listing all the available Messages.

This is Spring Boot code rather than anything related to the core function of a guest book application, so it too lives in the infrastructure layer.

package uk.co.a6software.guest_book.infrastructure.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import uk.co.a6software.guest_book.core.model.Message;
import uk.co.a6software.guest_book.core.model.MessageImpl;
import uk.co.a6software.guest_book.core.service.MessageService;
import uk.co.a6software.guest_book.infrastructure.dto.MessageRequest;

import java.util.List;

@Controller
public class MessageController {
    private final MessageService messageService;

    @Autowired
    public MessageController(@Qualifier("springMessageService") MessageService messageService) {
        this.messageService = messageService;
    }

    @PostMapping("/post")
    public ResponseEntity<String> postMessage(@RequestBody MessageRequest messageRequest) {
        MessageImpl message = new MessageImpl(messageRequest.getMessage());
        messageService.postMessage(messageRequest.getUser(), message);

        return ResponseEntity.ok(message.getMessage());
    }

    @GetMapping("/all")
    public ResponseEntity<List<Message>> getAllMessages() {
        return ResponseEntity.ok(this.messageService.getMessages());
    }
}
Code language: Java (java)

There are two interesting things happening here, which I have highlighted above.

The first is the use of the @Qualifier annotation in the constructor. We’ve already covered this above, but note here that this time I ask for the Spring variant.

The second is that I wanted to post in a couple of parameters, just to make things interesting:

  • the message body
  • the commentor’s name

There are several ways to send in data via post requests. Originally I only had one parameter – the message – so I could have done it as a query parameter (@RequestParam), I suppose. But this kind of fell flat when I thought about spaces, line breaks, punctuation etc. All possible, but horrible for me to encode manually for the sake of a simple example.

I quickly realised a far better way would be to send it as a JSON payload. Something like this:

json payload spring guest book example

Or, as cURL:

curl --location --request POST 'localhost:8080/post' \
--header 'Content-Type: application/json' \
--data-raw '{ "user": "billy bob", "message": "hello there" }'Code language: Shell Session (shell)

This is achieved by making use of the @RequestBody annotation.

You can do this in the controller action, like this:

    @PostMapping("/post")
    public ResponseEntity<String> postMessage(@RequestBody Map<String, Object> data) {
        String user = (String) data.get("user");
        String message = (String) data.get("message");

        messageService.postMessage(user, new MessageImpl(message));

        return ResponseEntity.ok("Received: " + message + " from " + user);
    }Code language: Java (java)

I’m not the biggest fan of this, hence why I went for the DTO approach.

But it’s possible.

Using a Data Transfer Object

However, I prefer to extract the mapping from JSON to a Java object into a separate class.

This class is called a DTO, or Data Transfer Object. The benefits are not so obvious on a small example like this, but the big one for me on large / real world projects is the ability to add validation. Other benefits are being able to control what data is sent back to clients, and re-use the same object in multiple places / requests, if needed, without having to duplicate logic.

Here’s the DTO:

// src/main/java/uk/co/a6software/guest_book/infrastructure/dto/MessageRequest.java

package uk.co.a6software.guest_book.infrastructure.dto;

public class MessageRequest {
    private String user;
    private String message;

    public MessageRequest(String user, String message) {
        this.user = user;
        this.message = message;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}Code language: Java (java)

Which I think we can all agree, is an exceptionally Java-y amount of code, for such a small task.

It was only during the write up of this post that it crossed my mind that I might be able to refactor this, quite signficiantly, and use a record instead:

// src/main/java/uk/co/a6software/guest_book/infrastructure/dto/MessageRequest.java

package uk.co.a6software.guest_book.infrastructure.dto;

public record MessageRequest(String user, String message) {
}Code language: Java (java)

And that is virtually identical in how it is used from the Controller’s point of view.

Here’s the change:

    @PostMapping("/post")
    public ResponseEntity<String> postMessage(@RequestBody MessageRequest messageRequest) {
        MessageImpl message = new MessageImpl(messageRequest.message());
        messageService.postMessage(messageRequest.user(), message);

        return ResponseEntity.ok(message.getMessage());
    }
Code language: Java (java)

It’s subtle, but instead of using getUser() and getMessage(), we instead just use user(), and message().

I quite like that.

Video Demo

Yes, it does.

It’s super basic, but we knew that from the start.

OK, so we aren’t winning any awards here, but it’s covered a bunch of common features that I’ve learned about from reading and watching Spring Boot tutorial videos, but haven’t yet put into practice.

Overall I’d say it’s a win.

Source Code

You can find the example source code here.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.