π Premium Read: Access my best content on Medium member-only articles β deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
β Some premium posts are free to read β no account needed. Follow me on Medium to stay updated and support my writing.
π Top 10 Udemy Courses (Huge Discount): Explore My Udemy Courses β Learn through real-time, project-based development.
βΆοΈ Subscribe to My YouTube Channel (172K+ subscribers): Java Guides on YouTube
π§Ύ Introduction
π Hey developers,
If youβre building Java microservices and want them to run smoothly in production, then this article is for you.
You can break a monolith into 10 microservices in a weekend.
But building scalable, resilient, and secure microservices?
Thatβs where most Java developers mess up.
They follow tutorialsβββbut skip the real-world best practices.
In this guide, weβll fix that. Youβll learn 10 essential Java microservices best practices, each explained with the mistake, best practice, and code examples.
I am a bestseller Udemy Instructor. Check out my top 10 Udemy courses with discounts: My Udemy CoursesβββRamesh Fadatare.
Here are five important microservices design patterns you should know in 2025, explained in simple terms with examplesβ¦medium.com
Letβs go π
1. Database Per Microservice
β Mistake
All services share a single database. One schema change breaks the entire system.
β Best Practice
Give each microservice its own dedicated database.
π‘ Real-World Example

-- Inventory DB
CREATE TABLE stock (
product_id VARCHAR(50) PRIMARY KEY,
quantity INT NOT NULL
);
-- Order DB
CREATE TABLE orders (
id UUID PRIMARY KEY,
product_id VARCHAR(50),
quantity INT
);
Why It Matters
- Avoids tight coupling
- Services can be scaled or replaced independently
- Enables true microservice isolation
The Database Per Service Pattern ensures that every microservice has its own dedicated database instead of sharing aβ¦medium.com
β 2. Use Event-Driven Communication
β Mistake
Synchronous REST calls between services create tight coupling and cascading failures.
β Best Practice
Use asynchronous communication with Kafka or RabbitMQ for critical flows.
π‘ Real-World Example
- Order Service publishes an
OrderPlaced
event - Payment Service consumes it and processes payment
- Inventory Service reserves stock on the same event
// OrderService - Event Publisher
kafkaTemplate.send("orderPlaced", new OrderPlacedEvent(orderId, productId, quantity));
// PaymentService - Event Consumer
@KafkaListener(topics = "orderPlaced")
public void handlePayment(OrderPlacedEvent event) {
paymentService.process(event.orderId());
}
Why It Matters
- Improves fault tolerance
- Services work independently
- Handles spikes better (no waiting on response)
Stop building tight-coupled microservices! Learn how to design true loosely coupled Java microservices usingβ¦rameshfadatare.medium.com
β 3. Use DTOsβββNever Expose JPA Entities
β Mistake
Exposing internal entities like Order
directly through your REST APIs.
β Best Practice
Use DTOs to clearly define your API contract.
π‘ Real-World Example
// Request DTO
public record CreateOrderRequest(String productId, int quantity) {}
// Response DTO
public record OrderResponse(UUID orderId, String status) {}
@PostMapping("/orders")
public ResponseEntity<OrderResponse> placeOrder(@RequestBody CreateOrderRequest request) {
Order order = orderService.placeOrder(request.productId(), request.quantity());
return ResponseEntity.ok(new OrderResponse(order.getId(), order.getStatus()));
}
Why It Matters
- Protects internal models
- Prevents leaking sensitive fields
- Allows APIs to evolve safely
In this tutorial, we will create a Spring Boot CRUD (Create, Read, Update, Delete) application using MySQL as theβ¦rameshfadatare.medium.com
β 4. Add Circuit Breakers, Timeouts, and Retries
β Mistake
Assuming all external services (like payment or inventory) will always respond.
β Best Practice
Wrap remote calls with circuit breakers and configure timeouts and fallbacks.
π‘ Real-World Example
If Payment Service is down, return a fallback response or retry.
Code Snippet (using Resilience4j)
@CircuitBreaker(name = "paymentService", fallbackMethod = "fallbackPayment")
public PaymentResponse callPaymentService(PaymentRequest request) {
return paymentClient.pay(request);
}
public PaymentResponse fallbackPayment(PaymentRequest req, Throwable ex) {
return new PaymentResponse("FAILED", "Payment service unavailable");
}
Why It Matters
- Avoids crashing the entire system
- Improves availability and graceful degradation
- Helps recover from temporary failures
Learn how to implement the Circuit Breaker pattern in Spring Boot 3+ using WebClient and Resilience4j. Build a completeβ¦rameshfadatare.medium.com
β 5. Centralized Logging and Monitoring
β Mistake
Logging only to the console or log fileβββno visibility across services.
β Best Practice
Use tools like ELK Stack, Prometheus, and Grafana for full observability.
π‘ Real-World Example
- Logs go to Elasticsearch
- Metrics go to Prometheus
- Traces show full request path across microservices
Why It Matters
- You can track bugs and performance issues
- Helps during outages
- Enables better alerting and monitoring
β 6. Make Event Consumers Idempotent
β Mistake
Assuming Kafka or RabbitMQ will deliver events only once.
β Best Practice
Make event handlers idempotentβββsafe to reprocess the same event.
π‘ Real-World Example
@KafkaListener(topics = "paymentSucceeded")
public void reserveStock(PaymentSucceededEvent event) {
if (!inventoryRepository.existsByOrderId(event.orderId())) {
inventoryRepository.reduceStock(event.productId(), event.quantity());
}
}
Why It Matters
- Prevents duplicate processing
- Keeps data consistent
- Ensures exactly-once-like behavior
β 7. Version Your APIs from Day One
β Mistake
Exposing /api/orders
and changing it without warningβββbreaking existing clients.
β Best Practice
Use API versioning: /api/v1/orders
, /api/v2/orders
, etc.
π‘ Real-World Example
@GetMapping("/api/v1/orders")
public OrderResponse getV1() {
// old structure
}
@GetMapping("/api/v2/orders")
public OrderDetails getV2() {
// new structure
}
Why It Matters
- Lets old clients continue working
- Supports smooth upgrades
- Avoids breaking production apps
β 8. Handle Graceful Shutdowns
β Mistake
Killing a service instantlyβββleaving messages unprocessed or DB connections open.
β Best Practice
Implement graceful shutdowns that finish in-flight tasks before exit.
π‘ Real-World Example
@PreDestroy
public void shutdown() {
kafkaConsumer.close();
connectionPool.close();
}
Why It Matters
- Prevents data loss
- Ensures safe deployments
- Avoids half-processed requests
β 9. Donβt Overuse Shared Libraries
β Mistake
Putting core logic, entities, and DB access into a shared βcommon-libβ.
β Best Practice
Limit shared libraries to DTOs or utility code only.
Avoid sharing database models and business logic.
π‘ Real-World Example
- β
Share
OrderPlacedEvent.java
- β Donβt share
OrderRepository.java
Why It Matters
- Prevents tight coupling
- Allows independent evolution
- Avoids one change breaking everything
β 10. Secure by Design
β Mistake
Leaving microservices wide openβββno auth, no rate limits, no encryption.
β Best Practice
Secure services using OAuth2, JWT, role-based access, and rate limiting.
π‘ Real-World Example
- Gateway validates JWT
- Downstream services check scopes and roles
- Rate limit per IP using Bucket4j
@Configuration
public class SecurityConfig {
// Use JWT filter here for token validation
}
Why It Matters
- Protects user data
- Prevents abuse
- Meets compliance requirements
Final Recap: Best Practices Table

π― Final Thoughts
β Microservices are not about splitting codeβββtheyβre about splitting ownership, responsibilities, and failures.
β Following best practices early saves months of pain later.
Good developers build microservices.
Great developers build microservices that are resilient, observable, scalable, and secure.
Focus on these 10 practicesβββand your Java microservices will truly shine in real-world production!
Comments
Post a Comment
Leave Comment