Quarkus Dev Services: Zero-config development
Quarkus Dev Services streamlines microservice development by automatically provisioning infrastructure like PostgreSQL, Kafka and Redis with zero config.
After years of Spring development, I've spent countless hours setting up local databases, message brokers and other infrastructure just to start coding.
Docker Compose files, environment variables, connection strings -- the yak shaving is all too familiar. Then, I discovered Quarkus Dev Services and it fundamentally changed how I approach development.
Let me paint a picture. Starting a new microservice project in the Spring world typically involves a checklist: write a docker-compose.yml for PostgreSQL, configure environment variables, update application.properties with connection details, ensure Docker is running, execute docker-compose up, and finally, start coding.
Quarkus Dev Services simplifies this. Whether you're building a simple REST API or a complex microservice architecture, Dev Services aims to remove the friction between you and your code.
What are Dev Services?
Dev Services represents Quarkus's philosophy of "focus on your code, we'll handle the infrastructure." When you include an extension that supports Dev Services, Quarkus automatically starts the required service using Testcontainers and wires it to your application. Developers don’t have to go through the manual configuration.
Consider a typical Spring Boot setup with PostgreSQL:
# application.properties in Spring Boot
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=myuser
spring.datasource.password=mypass
spring.jpa.hibernate.ddl-auto=update
Plus, you'd need a Docker Compose file to actually run PostgreSQL.
In Quarkus, you simply add the dependency:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
That's it.
Run ./mvnw quarkus:dev, and PostgreSQL starts automatically. The application connects without any configuration and Hibernate schema generation works out of the box.
Production configuration without the hassle
A common concern is handling the transition from development to production. Quarkus elegantly solves this with its profile system. Use Dev Services for development while maintaining an explicit production configuration:
# application.properties
# Dev mode uses Dev Services automatically - no config needed!
# Production configuration (only active with prod profile)
%prod.quarkus.datasource.db-kind=postgresql
%prod.quarkus.datasource.username=prod_user
%prod.quarkus.datasource.password=prod_pass
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://prod-db:5432/myapp
The %prod prefix ensures these properties only apply in production. During development, Dev Services takes over.
Beyond databases
What truly sets Dev Services apart is its comprehensive ecosystem support. Building a Kafka-based microservice? Simply add the extension:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kafka-client</artifactId>
</dependency>
A Kafka broker starts automatically with zero configuration. Need specific topics? Configure them declaratively:
# Kafka topics with custom partitions
mp.messaging.outgoing.product-updates.connector=smallrye-kafka
mp.messaging.outgoing.product-updates.topic=product-updates
mp.messaging.outgoing.product-updates.value.serializer=org.apache.kafka.common.serialization.StringSerializer
Redis for caching? Keycloak for authentication? MongoDB for document storage? They all follow the same pattern: add the extension, and the service starts automatically.
Building a real microservice with multiple services
Let's build a product catalog service that demonstrates the power of Dev Services by using PostgreSQL, Redis and Kafka together.
First, let’s generate the project:
mvn io.quarkus.platform:quarkus-maven-plugin:3.8.1:create \
-DprojectGroupId=ca.bazlur \
-DprojectArtifactId=product-catalog-service \
-DclassName="ca.bazlur.ProductResource" \
-Dpath="/products" \
-Dextensions="quarkus-rest,quarkus-redis-client,quarkus-jdbc-postgresql,quarkus-hibernate-orm-panache,quarkus-messaging-kafka,quarkus-rest-jackson,quarkus-hibernate-validator"
Alternatively we can go to the web-based project generator for Quarkus apps, then manually select the extension and download the project folder.
Now let's create our domain model:
package ca.bazlur;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import java.util.List;
@Entity
public class Product extends PanacheEntity {
public String name;
public String description;
public Double price;
public Integer stockQuantity;
@Enumerated(EnumType.STRING)
public Category category;
public enum Category {
ELECTRONICS, FURNITURE, CLOTHING, BOOKS, FOOD
}
public static List<Product> findByCategory(Category category) {
return list("category", category);
}
public static List<Product> findLowStock(int threshold) {
return list("stockQuantity < ?1", threshold);
}
}
Next, create a notification service for Kafka messaging:
package ca.bazlur;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import org.eclipse.microprofile.reactive.messaging.Channel;
import org.eclipse.microprofile.reactive.messaging.Emitter;
@ApplicationScoped
public class InventoryNotificationService {
@Inject
@Channel("product-updates")
Emitter<String> productUpdates;
public void notifyProductCreated(Product product) {
JsonObject event = Json.createObjectBuilder()
.add("eventType", "PRODUCT_CREATED")
.add("productId", product.id)
.add("name", product.name)
.add("category", product.category.toString())
.add("timestamp", System.currentTimeMillis())
.build();
productUpdates.send(event.toString());
}
public void notifyLowStock(Product product) {
JsonObject event = Json.createObjectBuilder()
.add("eventType", "LOW_STOCK_ALERT")
.add("productId", product.id)
.add("name", product.name)
.add("currentStock", product.stockQuantity)
.add("timestamp", System.currentTimeMillis())
.build();
productUpdates.send(event.toString());
}
}
Next, create a service that uses Redis for caching and Kafka for notifications:
package ca.bazlur;
import io.quarkus.redis.client.RedisClient;
import io.vertx.redis.client.Response;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import org.eclipse.microprofile.reactive.messaging.Channel;
import org.eclipse.microprofile.reactive.messaging.Emitter;
@ApplicationScoped
public class ProductService {
@Inject
RedisClient redisClient; // ①
@Inject
@Channel("product-updates")
Emitter<String> productUpdates; // ②
private static final String CACHE_PREFIX = "product:";
private static final int CACHE_TTL_SECONDS = 300;
@Transactional
public Product createProduct(String name, String description,
Double price, Integer stockQuantity,
Product.Category category) {
Product product = new Product();
product.name = name;
product.description = description;
product.price = price;
product.stockQuantity = stockQuantity;
product.category = category;
product.persist();
// Send Kafka notification
String notification = String.format(
"{\"event\":\"PRODUCT_CREATED\",\"id\":%d,\"name\":\"%s\"}",
product.id, product.name
);
productUpdates.send(notification); // ③
// Cache the product
cacheProduct(product);
return product;
}
public Product findById(Long id) {
// Try cache first
String cacheKey = CACHE_PREFIX + id;
Response cached = redisClient.get(cacheKey); // ④
if (cached != null) {
// In real app, deserialize from JSON
return Product.findById(id);
}
// Load from database and cache
Product product = Product.findById(id);
if (product != null) {
cacheProduct(product);
}
return product;
}
private void cacheProduct(Product product) {
String cacheKey = CACHE_PREFIX + product.id;
redisClient.setex(
cacheKey,
String.valueOf(CACHE_TTL_SECONDS),
product.name // Simplified - store JSON in real app
);
}
}
This service demonstrates the beauty of Dev Services in action:
① Redis client injection works immediately; Dev Services starts Redis automatically when it detects the redis-client extension.
② Kafka emitter for the product-updates channel is ready to use without any broker configuration.
③ Publishing messages to Kafka requires no setup; the broker, topics and partitions are created automatically.
④ Redis caching operations work out of the box with a real Redis instance, not a mock.
Finally, expose it through a REST endpoint:
package ca.bazlur;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/api/products")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ProductResource {
@Inject
ProductService productService;
@POST
public Response createProduct(CreateProductRequest request) {
Product product = productService.createProduct(
request.name(),
request.description(),
request.price(),
request.stockQuantity(),
request.category()
);
return Response.status(Response.Status.CREATED).entity(product).build();
}
@GET
@Path("/{id}")
public Response getProduct(@PathParam("id") Long id) {
Product product = productService.findById(id);
if (product != null) {
return Response.ok(product).build();
}
return Response.status(Response.Status.NOT_FOUND).build();
}
}
record CreateProductRequest(
String name,
String description,
Double price,
Integer stockQuantity,
Product.Category category
) {}
Now run the application:
./mvnw quarkus:dev
Quarkus automatically starts PostgreSQL, Redis and Kafka. Visit http://localhost:8080/q/dev-ui to see all running services in the Dev UI.
You can even add new extensions through the UI and Quarkus will add them to the project through pom.xml and reload automatically.
To see Dev Services in action, let's test our stock update endpoint. If you invoke the following endpoint:
### Update product stock
PUT http://localhost:8080/api/products/1/stock?quantity=5
You'll be able to see the Kafka messages in real-time through the Dev UI. Navigate to http://localhost:8080/q/dev-ui and click on the Kafka tile.
You can browse topics, see the messages being produced, and even consume them directly from the UI -- all without installing any Kafka tools or admin interfaces.
The Kafka Dev UI is especially useful for debugging. You can view message keys, values, headers and partition assignments for each message that your application produces.
For more details and supported services, visit the official Quarkus Dev Services guide.
A N M Bazlur Rahman is a Java Champion and staff software developer at DNAstack. He is also founder and moderator of the Java User Group in Bangladesh.