10 Java Microservices Best Practices Every Developer Should Follow βœ…

πŸ“˜ 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 discountsMy Udemy Coursesβ€Šβ€”β€ŠRamesh Fadatare.

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

βœ… 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)

βœ… 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

βœ… 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

βœ… 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

Spring Boot 3 Paid Course Published for Free
on my Java Guides YouTube Channel

Subscribe to my YouTube Channel (165K+ subscribers):
Java Guides Channel

Top 10 My Udemy Courses with Huge Discount:
Udemy Courses - Ramesh Fadatare