Quarkus Overview
Quarkus is a full-stack, Kubernetes-native Java framework made for Java virtual machines (JVMs) and native compilation, optimising Java specifically for containers and enabling it to become an effective platform for serverless, cloud, and Kubernetes environments. Quarkus is designed to work with popular Java standards, frameworks, and libraries like Eclipse MicroProfile and Spring, as well as Apache Kafka, RESTEasy (JAX-RS), Hibernate ORM (JPA), Spring, Infinispan, Camel, and many more.
Container-first
Whether an application is hosted on a public cloud or in an internally hosted Kubernetes cluster, characteristics like fast startup and low memory consumption are important to keeping overall host costs down.
Quarkus was built around a container-first philosophy, meaning it’s optimised for lower memory usage and faster startup times in the following ways:
- First-class support for Graal/SubstrateVM
- Build-time metadata processing
- Reduction in reflection usage
- Native image preboot
So Quarkus builds applications to consume 1/10th the memory when compared to traditional Java, and has a faster startup time (as much as 300 times faster), both of which greatly reduce the cost of cloud resources.
Quarkus is an effective solution for running Java in this new world of serverless architecture, microservices, containers, Kubernetes, function-as-a-service (FaaS), and cloud because it was created with all these things in mind.
Introduction
Whether an application is hosted on a public cloud or in an internally hosted Kubernetes cluster, characteristics like fast startup and low memory consumption are important to keeping overall host costs down.
Quarkus was built around a container-first philosophy, meaning it’s optimised for lower memory usage and faster startup times in the following ways:
- First-class support for Graal/SubstrateVM
- Build-time metadata processing
- Reduction in reflection usage
- Native image preboot
So Quarkus builds applications to consume 1/10th the memory when compared to traditional Java, and has a faster startup time (as much as 300 times faster), both of which greatly reduce the cost of cloud resources.
Quarkus is an effective solution for running Java in this new world of serverless architecture, microservices, containers, Kubernetes, function-as-a-service (FaaS), and cloud because it was created with all these things in mind.
Requirements
- JDK 11+
- GraalVM
- Docker
Maven dependencies
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-mockito</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
Controller Layer
@Path("/api/users")
public class UserController {
@Inject
UserService userService;
@GET
@Path("/{id}")
public User getUser(@PathParam("id") Long id) {
return userService.getUserById(id);
}
@GET
public List<User> getAllUsers() {
return userService.getUsers();
}
@POST
public User createUser(final User user) {
User createdUser = userService.createUser(user);
return createdUser;
}
@PUT
@Path("/{id}")
public User updateUser(@RestPath Long id, User user) {
return userService.updateUser(id, user);
}
@PATCH
@Path("/{id}")
public User patchUpdateUser(@RestPath Long id, User user) {
return userService.patchUpdateUser(id, user);
}
@DELETE
@Path("/{id}")
public void deleteUser(@RestPath Long id) {
userService.deleteUser(id);
}
}
Service Layer
@ApplicationScoped
public class UserServiceImpl implements UserService {
@Inject
private UsersRepository usersRepository;
@Override
public User getUserById(Long id) {
Optional<User> maybeUser = usersRepository.findById(id);
return maybeUser.orElseThrow(() -> new RuntimeException("User not found"));
}
@Override
public User createUser(User user) {
usersRepository.findByEmail(user.getEmail())
.ifPresent(p -> {
throw new RuntimeException("User with email " + p.getEmail() + " already exists ");
});
return usersRepository.save(user);
}
@Override
public User updateUser(Long id, User user) {
User userFound = usersRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));
userFound.setFirstName(user.getFirstName());
userFound.setLastName(user.getLastName());
userFound.setEmail(user.getEmail());
userFound.setFirstLineOfAddress(user.getFirstLineOfAddress());
userFound.setSecondLineOfAddress(user.getSecondLineOfAddress());
userFound.setTown(user.getTown());
userFound.setPostCode(user.getPostCode());
return usersRepository.save(userFound);
}
@Override
public User patchUpdateUser(Long id, User user) {
User userFound = usersRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));
ModelMapper modelMapper = new ModelMapper();
//copy only non null values
modelMapper.getConfiguration().setSkipNullEnabled(true).setMatchingStrategy(MatchingStrategies.STRICT);
modelMapper.map(user, userFound);
return usersRepository.save(userFound);
}
@Override
public void deleteUser(Long id) {
usersRepository.deleteById(id);
}
@Override
public List<User> getUsers() {
Iterable<User> userIterable = usersRepository.findAll();
return StreamSupport.stream(userIterable.spliterator(), false)
.collect(Collectors.toList());
}
}
Repository Layer
Entity
Entity/Persistence annotation name remain same as used in spring, but belongs to jakarta package
@Entity(name = "users")
public class User {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false, length = 50)
private String firstName;
@Column(nullable = false, length = 50)
private String lastName;
@Column(nullable = false, length = 120)
private String email;
@Column(nullable = false, length = 50)
private String firstLineOfAddress;
@Column(length = 50)
private String secondLineOfAddress;
@Column(nullable = false, length = 50)
private String town;
@Column(nullable = false, length = 10)
private String postCode;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPostCode() {
return postCode;
}
public void setPostCode(String postCode) {
this.postCode = postCode;
}
public String getFirstLineOfAddress() {
return firstLineOfAddress;
}
public void setFirstLineOfAddress(String firstLineOfAddress) {
this.firstLineOfAddress = firstLineOfAddress;
}
public String getTown() {
return town;
}
public void setTown(String town) {
this.town = town;
}
public String getSecondLineOfAddress() {
return secondLineOfAddress;
}
public void setSecondLineOfAddress(String secondLineOfAddress) {
this.secondLineOfAddress = secondLineOfAddress;
}
}
Repository Interface
Repository interface used is same as in spring
public interface UsersRepository extends CrudRepository<User, Long> {
Optional<User> findByEmail(String email);
}
Controller Layer Tests
@QuarkusTest
public class UserControllerTest {
@InjectMock
private UserService userService;
private User user;
private User user2;
@Inject
private ObjectMapper objectMapper;
@BeforeEach
void setup() {
user = new User();
user.setFirstName("Steve");
user.setLastName("Rob");
user.setEmail("steverob@gmail.com");
user.setId(1L);
user.setFirstLineOfAddress("12 Avenue");
user.setSecondLineOfAddress("Commercial street");
user.setTown("London");
user.setPostCode("HA6 0EW");
user2 = new User();
user2.setFirstName("Stev");
user2.setLastName("Ro");
user2.setEmail("stevob@gmail.com");
user2.setId(2L);
user2.setFirstLineOfAddress("13 Avenue");
user2.setSecondLineOfAddress("Commercial street");
user2.setTown("London");
user2.setPostCode("HA7 0EW");
}
@Test
void testGetUser() throws Exception {
String expected = objectMapper.writeValueAsString(user);
when(userService.getUserById(anyLong())).thenReturn(user);
var response = given().contentType(ContentType.JSON)
.when()
.get("/api/users/" + user.getId())
.then()
.statusCode(Response.Status.OK.getStatusCode())
.extract();
assertEquals(expected, response.asString());
}
@Test
public void testGetAllUsers() throws Exception {
List<User> users = Arrays.asList(user, user2);
String expected = objectMapper.writeValueAsString(users);
when(userService.getUsers()).thenReturn(users);
var response = given().contentType(ContentType.JSON)
.when()
.get("/api/users")
.then()
.statusCode(Response.Status.OK.getStatusCode())
.extract();
assertEquals(expected, response.asString());
}
@Test
void testCreateUser() throws Exception {
String expected = objectMapper.writeValueAsString(user);
String request = expected.replace("\"id\":1,", "");
when(userService.createUser(any(User.class))).thenReturn(user);
var response = given().contentType(ContentType.JSON)
.accept(ContentType.JSON)
.body(request)
.when()
.post("/api/users")
.then()
.statusCode(Response.Status.OK.getStatusCode())
.extract();
assertEquals(expected, response.asString());
}
@Test
void testUpdateUser() throws Exception {
User updateUser = new User();
updateUser.setFirstName("Steveupdated");
updateUser.setLastName("Robupdated");
updateUser.setEmail("steverobupdated@gmail.com");
updateUser.setFirstLineOfAddress("12 Avenueupdated");
updateUser.setSecondLineOfAddress("Commercial streetupdated");
updateUser.setTown("London");
updateUser.setPostCode("HA6 0EW");
User updateUserWithId = new User();
new ModelMapper().map(updateUser, updateUserWithId);
updateUserWithId.setId(1L);
//Exclude null values
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
String putBody = objectMapper.writeValueAsString(updateUser);
String expectedResponse = objectMapper.writeValueAsString(updateUserWithId);
when(userService.updateUser(anyLong(), any(User.class))).thenReturn(updateUserWithId);
var response = given().contentType(ContentType.JSON)
.accept(ContentType.JSON)
.body(putBody)
.when()
.put("/api/users/{userId}", updateUserWithId.getId())
.then()
.statusCode(Response.Status.OK.getStatusCode())
.extract();
assertEquals(expectedResponse, response.asString());
}
@Test
void testPatchUpdateUser() throws Exception {
User updateUser = new User();
updateUser.setFirstName("Steveupdated");
updateUser.setLastName("Robupdated");
user.setFirstName(updateUser.getFirstName());
user.setLastName(updateUser.getLastName());
//Exclude null values
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
String patchBody = objectMapper.writeValueAsString(updateUser);
String expectedResponse = objectMapper.writeValueAsString(user);
when(userService.patchUpdateUser(anyLong(), any(User.class))).thenReturn(user);
var response = given().contentType(ContentType.JSON)
.accept(ContentType.JSON)
.body(patchBody)
.when()
.patch("/api/users/{userId}", user.getId())
.then()
.statusCode(Response.Status.OK.getStatusCode())
.extract();
assertEquals(expectedResponse, response.asString());
}
@Test
void testDeleteUser() throws Exception {
doNothing().when(userService).deleteUser(anyLong());
var response = given().contentType(ContentType.JSON)
.accept(ContentType.JSON)
.when()
.delete("/api/users/{userId}", user.getId())
.then()
.statusCode(Response.Status.NO_CONTENT.getStatusCode())
.extract();
assertEquals("", response.asString());
}
}

Service Layer Tests
@QuarkusTest
class UserServiceImplTest {
@InjectMocks
private UserServiceImpl userService;
@Mock
private UsersRepository usersRepository;
private User user;
private User user2;
@BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
user = new User();
user.setId(1L);
user.setFirstName("Steve");
user.setLastName("Rob");
user.setEmail("steverob@gmail.com");
user.setFirstLineOfAddress("10 Oliver");
user.setSecondLineOfAddress("Commercial street");
user.setTown("London");
user.setPostCode("HA4 7EW");
user2 = new User();
user2.setId(2L);
user2.setFirstName("Tony");
user2.setLastName("Taylor");
user2.setEmail("tonytaylor@gmail.com");
user2.setFirstLineOfAddress("12 Avenue");
user2.setSecondLineOfAddress("Avenue street");
user2.setTown("London");
user2.setPostCode("HA6 0EW");
}
@Test
void testGetUserById() {
when(usersRepository.findById(anyLong())).thenReturn(Optional.of(user));
User userFound = userService.getUserById(user.getId());
assertNotNull(userFound);
assertEquals(user.getId(), userFound.getId());
assertEquals(user.getEmail(), userFound.getEmail());
assertEquals(user.getFirstName(), userFound.getFirstName());
assertEquals(user.getLastName(), userFound.getLastName());
assertEquals(user.getPostCode(), userFound.getPostCode());
assertEquals(user.getFirstLineOfAddress(), userFound.getFirstLineOfAddress());
assertEquals(user.getSecondLineOfAddress(), userFound.getSecondLineOfAddress());
assertEquals(user.getTown(), userFound.getTown());
}
@Test
void shouldThrowExceptionWhenGetUserHasWrongId() {
when(usersRepository.findById(anyLong())).thenReturn(Optional.empty());
assertThrows(RuntimeException.class, () -> userService.getUserById(10000L));
}
@Test
void testGetUsers() {
List<User> users = Arrays.asList(user, user2);
when(usersRepository.findAll()).thenReturn(users);
List<User> usersReceived = userService.getUsers();
assertEquals(users, usersReceived);
assertEquals(users.size(), usersReceived.size());
assertEquals(users.get(0).getId(), usersReceived.get(0).getId());
assertEquals(users.get(0).getFirstName(), usersReceived.get(0).getFirstName());
assertEquals(users.get(0).getLastName(), usersReceived.get(0).getLastName());
assertEquals(users.get(0).getEmail(), usersReceived.get(0).getEmail());
assertEquals(users.get(0).getFirstLineOfAddress(), usersReceived.get(0).getFirstLineOfAddress());
assertEquals(users.get(0).getSecondLineOfAddress(), usersReceived.get(0).getSecondLineOfAddress());
assertEquals(users.get(0).getSecondLineOfAddress(), usersReceived.get(0).getSecondLineOfAddress());
}
@Test
void testCreateUser() {
when(usersRepository.findByEmail(anyString())).thenReturn(Optional.empty());
when(usersRepository.save(any(User.class))).thenReturn(user);
User createNewUser = new User();
createNewUser.setFirstName(user.getFirstName());
createNewUser.setLastName(user.getLastName());
createNewUser.setEmail(user.getEmail());
createNewUser.setPostCode(user.getPostCode());
createNewUser.setFirstLineOfAddress(user.getFirstLineOfAddress());
createNewUser.setSecondLineOfAddress(user.getSecondLineOfAddress());
createNewUser.setTown(user.getTown());
User createdUser = userService.createUser(createNewUser);
assertNotNull(createdUser);
assertEquals(user.getId(), createdUser.getId());
assertEquals(user.getEmail(), createdUser.getEmail());
assertEquals(user.getFirstName(), createdUser.getFirstName());
assertEquals(user.getLastName(), createdUser.getLastName());
assertEquals(user.getPostCode(), createdUser.getPostCode());
assertEquals(user.getFirstLineOfAddress(), createdUser.getFirstLineOfAddress());
assertEquals(user.getSecondLineOfAddress(), createdUser.getSecondLineOfAddress());
assertEquals(user.getTown(), createdUser.getTown());
}
@Test
void testUpdateUser() {
User updateUser = new User();
updateUser.setFirstName("Steve");
updateUser.setLastName("Rob");
updateUser.setEmail("steverob@gmail.com");
updateUser.setPostCode("HA5 2EW");
updateUser.setFirstLineOfAddress("10 Oliver");
updateUser.setSecondLineOfAddress("Commercial street");
updateUser.setTown("London");
User updateUserWithId = new User();
new ModelMapper().map(updateUser, updateUserWithId);
updateUserWithId.setId(1L);
when(usersRepository.findById(anyLong())).thenReturn(Optional.of(updateUserWithId));
when(usersRepository.save(any(User.class))).thenReturn(updateUserWithId);
User savedUser = userService.updateUser(updateUserWithId.getId(), updateUser);
assertNotNull(savedUser);
assertEquals(updateUserWithId.getId(), savedUser.getId());
assertEquals(updateUserWithId.getEmail(), savedUser.getEmail());
assertEquals(updateUserWithId.getFirstName(), savedUser.getFirstName());
assertEquals(updateUserWithId.getLastName(), savedUser.getLastName());
assertEquals(updateUserWithId.getPostCode(), savedUser.getPostCode());
assertEquals(updateUserWithId.getFirstLineOfAddress(), savedUser.getFirstLineOfAddress());
assertEquals(updateUserWithId.getSecondLineOfAddress(), savedUser.getSecondLineOfAddress());
assertEquals(updateUserWithId.getTown(), savedUser.getTown());
}
@Test
void testPatchUpdateUser() {
User updateUser = new User();
updateUser.setFirstName("Steveupdated");
updateUser.setLastName("Robupdated");
user.setFirstName(updateUser.getFirstName());
user.setLastName(updateUser.getLastName());
when(usersRepository.findById(anyLong())).thenReturn(Optional.of(user));
when(usersRepository.save(any(User.class))).thenReturn(user);
User savedUser = userService.patchUpdateUser(user.getId(), updateUser);
assertNotNull(savedUser);
assertEquals(user.getId(), savedUser.getId());
assertEquals(user.getEmail(), savedUser.getEmail());
assertEquals(user.getFirstName(), savedUser.getFirstName());
assertEquals(user.getLastName(), savedUser.getLastName());
assertEquals(user.getPostCode(), savedUser.getPostCode());
assertEquals(user.getFirstLineOfAddress(), savedUser.getFirstLineOfAddress());
assertEquals(user.getSecondLineOfAddress(), savedUser.getSecondLineOfAddress());
assertEquals(user.getTown(), savedUser.getTown());
}
@Test
void testDeleteUser() {
when(usersRepository.findById(anyLong())).thenReturn(Optional.of(user));
doNothing().when(usersRepository).delete(any(User.class));
userService.deleteUser(user.getId());
verify(usersRepository, times(1)).deleteById(user.getId());
}
}

Repository Layer Tests
@QuarkusTest
public class UserRepositoryTest {
@Inject
private UsersRepository usersRepository;
@Inject
UserService userService;
@Test
void testFndByEmail() {
//Given
User user = new User();
user.setFirstName("Steve");
user.setLastName("Rob");
user.setEmail(UUID.randomUUID().toString() + "@gmail.com");
user.setFirstLineOfAddress("10 Oliver");
user.setSecondLineOfAddress("Commercial street");
user.setTown("London");
user.setPostCode("HA4 7EW");
userService.createUser(user);
//When
User found = usersRepository.findByEmail(user.getEmail()).get();
//Then
assertEquals(user.getEmail(), found.getEmail());
}
}

Integration Tests
@QuarkusTest
public class UserRestIT {
private User user;
@Inject
private ObjectMapper objectMapper;
@BeforeEach
void setup() {
user = new User();
user.setFirstName("Steve");
user.setLastName("Rob");
user.setEmail(UUID.randomUUID().toString() + "@gmail.com");
user.setId(1L);
user.setFirstLineOfAddress("12 Avenue");
user.setSecondLineOfAddress("Commercial street");
user.setTown("London");
user.setPostCode("HA6 0EW");
}
@Test
public void testCreateUser() throws Exception {
String expected = objectMapper.writeValueAsString(user);
String request = expected.replace("\"id\":1,", "");
given().contentType(ContentType.JSON)
.accept(ContentType.JSON)
.body(request)
.when()
.post("/api/users")
.then()
.statusCode(Response.Status.OK.getStatusCode())
.body("firstName", is(user.getFirstName()))
.body("lastName", is(user.getLastName()))
.body("email", is(user.getEmail()))
.body("firstLineOfAddress", is(user.getFirstLineOfAddress()))
.body("secondLineOfAddress", is(user.getSecondLineOfAddress()))
.body("town", is(user.getTown()))
.body("postCode", is(user.getPostCode()));
}
}
