Published on

The Complete Guide to Modern Java Architecture - Part 3: Implementation Deep Dives

Authors

The Complete Guide to Modern Java Architecture - Part 3: Implementation Deep Dives

This is Part 3 of a comprehensive 5-part series on Modern Java Architecture. Building on the architectural patterns from Part 2, we now dive deep into the implementation details that make systems production-ready.

Series Overview:

  • Part 1: Foundation - Evolution, principles, and modern Java features
  • Part 2: Architecture Patterns - Monoliths, microservices, and event-driven design
  • Part 3: Implementation Deep Dives (This post) - APIs, data layer, security, and observability
  • Part 4: Performance & Scalability - Optimization, reactive programming, and scaling patterns
  • Part 5: Production Considerations - Deployment, containers, and operational excellence

Architecture patterns provide the blueprint, but implementation details determine whether your system succeeds in production. After years of debugging midnight outages, performance bottlenecks, and security incidents, I've learned that robust implementation is what separates theoretical architectures from production-ready systems.

This part focuses on the four pillars of implementation excellence: API design, data management, security, and observability. These aren't separate concerns—they're interconnected aspects that must work together seamlessly.

API Design: Your System's Contract

API-First Development in 2025

Modern systems start with API contracts, not implementation. This approach enables parallel development, clear boundaries, and evolutionary design.

# OpenAPI 3.1 specification
openapi: 3.1.0
info:
  title: Order Management API
  version: 2.0.0
  description: Comprehensive order management with event-driven capabilities
servers:
  - url: https://api.example.com/v2
    description: Production server
  - url: https://staging-api.example.com/v2
    description: Staging server

paths:
  /orders:
    post:
      operationId: createOrder
      summary: Create a new order
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOrderRequest'
            examples:
              simple_order:
                summary: Simple product order
                value:
                  customerId: "cust_12345"
                  items:
                    - productId: "prod_67890"
                      quantity: 2
                      unitPrice: 29.99
      responses:
        '201':
          description: Order created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
          headers:
            Location:
              description: URL of the created order
              schema:
                type: string
                format: uri
        '400':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'

components:
  schemas:
    CreateOrderRequest:
      type: object
      required: [customerId, items]
      properties:
        customerId:
          type: string
          pattern: '^cust_[a-zA-Z0-9]+$'
          example: "cust_12345"
        items:
          type: array
          minItems: 1
          maxItems: 50
          items:
            $ref: '#/components/schemas/OrderItem'
        shippingAddress:
          $ref: '#/components/schemas/Address'
        metadata:
          type: object
          additionalProperties:
            type: string
          maxProperties: 10
      additionalProperties: false

Generated Code with Modern Java Features:

// Generated from OpenAPI specification
public record CreateOrderRequest(
    @Pattern(regexp = "^cust_[a-zA-Z0-9]+$")
    @NotNull String customerId,
    
    @Valid @NotEmpty @Size(max = 50)
    List<OrderItem> items,
    
    @Valid Address shippingAddress,
    
    @Size(max = 10)
    Map<String, String> metadata
) {
    public CreateOrderRequest {
        // Compact constructor validation
        Objects.requireNonNull(customerId, "Customer ID cannot be null");
        Objects.requireNonNull(items, "Items cannot be null");
        
        if (items.isEmpty()) {
            throw new IllegalArgumentException("Order must contain at least one item");
        }
    }
}

// Domain-driven implementation
@RestController
@RequestMapping("/api/v2/orders")
@Validated
public class OrderController {
    
    private final OrderService orderService;
    private final OrderMapper orderMapper;
    
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public ResponseEntity<OrderResponse> createOrder(
            @Valid @RequestBody CreateOrderRequest request,
            HttpServletRequest httpRequest) {
        
        // Convert to domain command
        CreateOrderCommand command = orderMapper.toCommand(request);
        
        // Execute business logic
        Order order = orderService.createOrder(command);
        
        // Convert to response DTO
        OrderResponse response = orderMapper.toResponse(order);
        
        // Build response with proper headers
        URI location = ServletUriComponentsBuilder
            .fromCurrentRequest()
            .path("/{id}")
            .buildAndExpand(order.getId())
            .toUri();
            
        return ResponseEntity
            .created(location)
            .header("X-Request-ID", httpRequest.getHeader("X-Request-ID"))
            .body(response);
    }
}

API Versioning Strategies

Semantic Versioning with Backward Compatibility:

// Version-aware controller
@RestController
@RequestMapping("/api")
public class VersionedOrderController {
    
    // Version 1 - Legacy support
    @PostMapping(value = "/v1/orders", 
                 produces = "application/vnd.api.v1+json")
    public OrderV1Response createOrderV1(@RequestBody CreateOrderV1Request request) {
        // Convert V1 request to current domain model
        CreateOrderCommand command = v1Adapter.adapt(request);
        Order order = orderService.createOrder(command);
        return v1Adapter.adapt(order);
    }
    
    // Version 2 - Current
    @PostMapping(value = "/v2/orders",
                 produces = "application/vnd.api.v2+json")
    public OrderV2Response createOrderV2(@RequestBody CreateOrderV2Request request) {
        CreateOrderCommand command = v2Adapter.adapt(request);
        Order order = orderService.createOrder(command);
        return v2Adapter.adapt(order);
    }
    
    // Future version - with feature flags
    @PostMapping(value = "/v3/orders",
                 produces = "application/vnd.api.v3+json")
    @ConditionalOnProperty(name = "api.v3.enabled", havingValue = "true")
    public OrderV3Response createOrderV3(@RequestBody CreateOrderV3Request request) {
        // New features available in V3
        CreateOrderCommand command = v3Adapter.adapt(request);
        Order order = orderService.createOrder(command);
        return v3Adapter.adapt(order);
    }
}

// Adapter pattern for version translation
@Component
public class OrderV1Adapter {
    
    public CreateOrderCommand adapt(CreateOrderV1Request v1Request) {
        return CreateOrderCommand.builder()
            .customerId(v1Request.customerId())
            .items(adaptItems(v1Request.items()))
            // V1 didn't have shipping address - use default
            .shippingAddress(getDefaultShippingAddress(v1Request.customerId()))
            // V1 didn't have metadata - empty map
            .metadata(Collections.emptyMap())
            .build();
    }
    
    public OrderV1Response adapt(Order order) {
        return new OrderV1Response(
            order.getId().toString(),
            order.getCustomerId(),
            adaptItemsToV1(order.getItems()),
            order.getTotal().getAmount(),
            order.getStatus().name()
        );
    }
}

Modern API Patterns

GraphQL for Complex Data Requirements:

// GraphQL schema definition
@Component
public class OrderGraphQLResolver implements GraphQLQueryResolver, GraphQLMutationResolver {
    
    private final OrderService orderService;
    private final CustomerService customerService;
    
    // Query resolver with DataLoader for N+1 prevention
    public List<Order> getOrders(String customerId, 
                                OrderStatus status, 
                                DataFetchingEnvironment env) {
        
        DataLoader<String, Customer> customerLoader = 
            env.getDataLoader("customerLoader");
            
        return orderService.findOrders(customerId, status)
            .stream()
            .peek(order -> customerLoader.load(order.getCustomerId()))
            .collect(toList());
    }
    
    // Mutation with optimistic locking
    public Order updateOrderStatus(String orderId, 
                                  OrderStatus newStatus, 
                                  Integer version) {
        UpdateOrderStatusCommand command = new UpdateOrderStatusCommand(
            OrderId.of(orderId), newStatus, version);
        return orderService.updateStatus(command);
    }
    
    // Subscription for real-time updates
    @SchemaMapping(typeName = "Subscription", field = "orderUpdates")
    public Publisher<Order> orderUpdates(@Argument String customerId) {
        return orderEventPublisher
            .getOrderUpdatesFor(customerId)
            .map(this::convertToOrder);
    }
}

// DataLoader configuration for efficient batching
@Configuration
public class GraphQLDataLoaderConfig {
    
    @Bean
    public DataLoader<String, Customer> customerDataLoader(CustomerService customerService) {
        return DataLoaderFactory.newDataLoader(customerIds -> 
            CompletableFuture.supplyAsync(() -> 
                customerService.findByIds(customerIds)));
    }
}

gRPC for Internal Service Communication:

// order_service.proto
syntax = "proto3";

package com.example.orders;

import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";

service OrderService {
  rpc CreateOrder(CreateOrderRequest) returns (Order);
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc StreamOrderUpdates(StreamOrderUpdatesRequest) returns (stream OrderUpdate);
}

message CreateOrderRequest {
  string customer_id = 1;
  repeated OrderItem items = 2;
  Address shipping_address = 3;
  map<string, string> metadata = 4;
}

message Order {
  string id = 1;
  string customer_id = 2;
  repeated OrderItem items = 3;
  OrderStatus status = 4;
  google.protobuf.Timestamp created_at = 5;
  Money total = 6;
}
// gRPC service implementation
@GrpcService
public class OrderGrpcService extends OrderServiceGrpc.OrderServiceImplBase {
    
    private final OrderService orderService;
    private final OrderProtoMapper mapper;
    
    @Override
    public void createOrder(CreateOrderRequest request, 
                           StreamObserver<Order> responseObserver) {
        try {
            // Convert from protobuf to domain
            CreateOrderCommand command = mapper.toDomain(request);
            
            // Execute business logic
            com.example.domain.Order domainOrder = orderService.createOrder(command);
            
            // Convert back to protobuf
            Order protoOrder = mapper.toProto(domainOrder);
            
            responseObserver.onNext(protoOrder);
            responseObserver.onCompleted();
            
        } catch (ValidationException e) {
            responseObserver.onError(Status.INVALID_ARGUMENT
                .withDescription(e.getMessage())
                .asRuntimeException());
        } catch (Exception e) {
            responseObserver.onError(Status.INTERNAL
                .withDescription("Internal server error")
                .asRuntimeException());
        }
    }
    
    @Override
    public void streamOrderUpdates(StreamOrderUpdatesRequest request,
                                  StreamObserver<OrderUpdate> responseObserver) {
        
        Flux<OrderUpdate> updateStream = orderEventStream
            .getUpdatesFor(request.getCustomerId())
            .map(mapper::toOrderUpdate)
            .doOnCancel(() -> log.info("Client cancelled stream"))
            .doOnError(error -> log.error("Stream error", error));
            
        updateStream.subscribe(
            responseObserver::onNext,
            responseObserver::onError,
            responseObserver::onCompleted
        );
    }
}

Data Layer: Consistency and Performance

Multi-Model Data Architecture

Modern applications require different data models for different use cases:

// Command side - Optimized for writes
@Entity
@Table(name = "orders")
public class OrderEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    
    @Column(name = "customer_id", nullable = false)
    private String customerId;
    
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private List<OrderItemEntity> items = new ArrayList<>();
    
    @Enumerated(EnumType.STRING)
    private OrderStatus status;
    
    @Version
    private Integer version;
    
    @CreationTimestamp
    private Instant createdAt;
    
    @UpdateTimestamp
    private Instant updatedAt;
    
    // Domain logic methods
    public void addItem(OrderItemEntity item) {
        items.add(item);
        item.setOrder(this);
    }
    
    public Money calculateTotal() {
        return items.stream()
            .map(OrderItemEntity::getSubtotal)
            .reduce(Money.ZERO, Money::add);
    }
}

// Query side - Optimized for reads
@Document(collection = "order_views")
public class OrderView {
    
    @Id
    private String id;
    
    @Indexed
    private String customerId;
    
    private String customerName;
    private String customerEmail;
    
    private List<OrderItemView> items;
    private BigDecimal totalAmount;
    private String currency;
    private OrderStatus status;
    
    @Indexed
    private LocalDateTime createdAt;
    
    // Denormalized for fast queries
    private CustomerSummary customerSummary;
    private List<String> productCategories;
    private Map<String, Object> searchableAttributes;
}

// Repository pattern with multiple implementations
public interface OrderRepository {
    Order save(Order order);
    Optional<Order> findById(OrderId id);
    List<Order> findByCustomerId(String customerId);
}

@Repository
@Transactional
public class JpaOrderRepository implements OrderRepository {
    
    private final OrderJpaRepository jpaRepository;
    private final OrderEntityMapper mapper;
    
    @Override
    public Order save(Order order) {
        OrderEntity entity = mapper.toEntity(order);
        OrderEntity saved = jpaRepository.save(entity);
        return mapper.toDomain(saved);
    }
    
    @Override
    @Transactional(readOnly = true)
    public Optional<Order> findById(OrderId id) {
        return jpaRepository.findById(id.getValue())
            .map(mapper::toDomain);
    }
}

// Read-optimized repository
@Repository
public class MongoOrderViewRepository {
    
    private final MongoTemplate mongoTemplate;
    
    public List<OrderView> findByCustomerIdWithPagination(String customerId, 
                                                         Pageable pageable) {
        Query query = Query.query(Criteria.where("customerId").is(customerId))
            .with(pageable)
            .with(Sort.by(Direction.DESC, "createdAt"));
            
        return mongoTemplate.find(query, OrderView.class);
    }
    
    public List<OrderView> searchOrders(OrderSearchCriteria criteria) {
        Criteria searchCriteria = new Criteria();
        
        if (criteria.getCustomerName() != null) {
            searchCriteria.and("customerName")
                .regex(criteria.getCustomerName(), "i");
        }
        
        if (criteria.getStatus() != null) {
            searchCriteria.and("status").is(criteria.getStatus());
        }
        
        if (criteria.getDateRange() != null) {
            searchCriteria.and("createdAt")
                .gte(criteria.getDateRange().getStart())
                .lte(criteria.getDateRange().getEnd());
        }
        
        Query query = Query.query(searchCriteria);
        return mongoTemplate.find(query, OrderView.class);
    }
}

Event Sourcing and CQRS Implementation

// Event Store implementation
@Component
public class EventStore {
    
    private final EventJpaRepository eventRepository;
    private final EventSerializer eventSerializer;
    
    public void saveEvents(String aggregateId, 
                          List<DomainEvent> events, 
                          int expectedVersion) {
        
        // Optimistic locking check
        int currentVersion = getCurrentVersion(aggregateId);
        if (currentVersion != expectedVersion) {
            throw new ConcurrencyException(
                "Expected version " + expectedVersion + 
                " but was " + currentVersion);
        }
        
        // Save events
        List<EventEntity> eventEntities = events.stream()
            .map(event -> EventEntity.builder()
                .aggregateId(aggregateId)
                .eventType(event.getClass().getSimpleName())
                .eventData(eventSerializer.serialize(event))
                .eventVersion(++currentVersion)
                .timestamp(Instant.now())
                .build())
            .collect(toList());
            
        eventRepository.saveAll(eventEntities);
        
        // Publish events for projections
        events.forEach(eventPublisher::publish);
    }
    
    public Stream<DomainEvent> getEvents(String aggregateId) {
        return eventRepository.findByAggregateIdOrderByEventVersion(aggregateId)
            .stream()
            .map(entity -> eventSerializer.deserialize(
                entity.getEventData(), entity.getEventType()));
    }
}

// Aggregate root with event sourcing
public class Order {
    
    private OrderId id;
    private String customerId;
    private List<OrderItem> items = new ArrayList<>();
    private OrderStatus status;
    private int version;
    
    // Events to be saved
    private List<DomainEvent> uncommittedEvents = new ArrayList<>();
    
    // Apply events for state reconstruction
    public void apply(DomainEvent event) {
        switch (event) {
            case OrderCreatedEvent e -> {
                this.id = e.orderId();
                this.customerId = e.customerId();
                this.items = new ArrayList<>(e.items());
                this.status = OrderStatus.PENDING;
            }
            case OrderItemAddedEvent e -> {
                this.items.add(e.item());
            }
            case OrderConfirmedEvent e -> {
                this.status = OrderStatus.CONFIRMED;
            }
            case OrderCancelledEvent e -> {
                this.status = OrderStatus.CANCELLED;
            }
            default -> throw new IllegalArgumentException("Unknown event: " + event);
        }
        version++;
    }
    
    // Command handlers that generate events
    public static Order create(CreateOrderCommand command) {
        Order order = new Order();
        OrderCreatedEvent event = new OrderCreatedEvent(
            command.orderId(),
            command.customerId(),
            command.items(),
            Instant.now()
        );
        
        order.apply(event);
        order.uncommittedEvents.add(event);
        return order;
    }
    
    public void confirm() {
        if (status != OrderStatus.PENDING) {
            throw new IllegalStateException("Only pending orders can be confirmed");
        }
        
        OrderConfirmedEvent event = new OrderConfirmedEvent(id, Instant.now());
        apply(event);
        uncommittedEvents.add(event);
    }
}

// Projection handler for read models
@Component
public class OrderProjectionHandler {
    
    private final OrderViewRepository orderViewRepository;
    private final CustomerService customerService;
    
    @EventHandler
    @Async
    public void handle(OrderCreatedEvent event) {
        // Build denormalized view
        Customer customer = customerService.findById(event.customerId());
        
        OrderView view = OrderView.builder()
            .id(event.orderId().toString())
            .customerId(event.customerId())
            .customerName(customer.getName())
            .customerEmail(customer.getEmail())
            .items(event.items().stream()
                .map(this::toOrderItemView)
                .collect(toList()))
            .status(OrderStatus.PENDING)
            .createdAt(event.occurredAt().atZone(ZoneOffset.UTC).toLocalDateTime())
            .searchableAttributes(buildSearchableAttributes(event, customer))
            .build();
            
        orderViewRepository.save(view);
    }
    
    @EventHandler
    @Async
    public void handle(OrderConfirmedEvent event) {
        orderViewRepository.updateStatus(
            event.orderId().toString(), 
            OrderStatus.CONFIRMED
        );
    }
}

Distributed Transaction Patterns

// Saga pattern implementation
@Component
public class OrderProcessingSaga {
    
    private final SagaManager sagaManager;
    private final PaymentService paymentService;
    private final InventoryService inventoryService;
    private final OrderService orderService;
    
    @SagaStart
    public void handle(OrderCreatedEvent event) {
        SagaTransaction saga = sagaManager.begin(
            "order-processing", 
            event.orderId().toString()
        );
        
        // Step 1: Reserve inventory
        saga.step("reserve-inventory")
            .invoke(() -> inventoryService.reserve(event.items()))
            .compensate(() -> inventoryService.release(event.items()));
            
        // Step 2: Process payment
        saga.step("process-payment")
            .invoke(() -> paymentService.charge(event.paymentDetails()))
            .compensate(() -> paymentService.refund(event.paymentDetails()));
            
        // Step 3: Confirm order
        saga.step("confirm-order")
            .invoke(() -> orderService.confirm(event.orderId()))
            .compensate(() -> orderService.cancel(event.orderId(), "Saga rollback"));
            
        saga.execute();
    }
}

// Outbox pattern for reliable event publishing
@Component
@Transactional
public class OutboxEventPublisher {
    
    private final OutboxRepository outboxRepository;
    
    public void publishEvent(DomainEvent event, String aggregateId) {
        // Save to outbox table in same transaction
        OutboxEvent outboxEvent = OutboxEvent.builder()
            .aggregateId(aggregateId)
            .eventType(event.getClass().getSimpleName())
            .eventData(serialize(event))
            .createdAt(Instant.now())
            .processed(false)
            .build();
            
        outboxRepository.save(outboxEvent);
    }
}

// Outbox processor - separate component
@Component
public class OutboxProcessor {
    
    @Scheduled(fixedDelay = 5000) // Every 5 seconds
    @Transactional
    public void processOutboxEvents() {
        List<OutboxEvent> unprocessedEvents = outboxRepository
            .findByProcessedFalseOrderByCreatedAt(PageRequest.of(0, 100));
            
        for (OutboxEvent outboxEvent : unprocessedEvents) {
            try {
                DomainEvent event = deserialize(
                    outboxEvent.getEventData(), 
                    outboxEvent.getEventType()
                );
                
                // Publish to message broker
                eventPublisher.publish(event);
                
                // Mark as processed
                outboxEvent.setProcessed(true);
                outboxRepository.save(outboxEvent);
                
            } catch (Exception e) {
                log.error("Failed to process outbox event: {}", 
                    outboxEvent.getId(), e);
                // Retry logic or dead letter queue
            }
        }
    }
}

Security: Defense in Depth

Authentication and Authorization

// JWT-based authentication with modern Spring Security
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session -> 
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers(HttpMethod.POST, "/api/orders").hasRole("USER")
                .requestMatchers(HttpMethod.GET, "/api/orders/**").hasAnyRole("USER", "ADMIN")
                .anyRequest().authenticated())
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .decoder(jwtDecoder())
                    .jwtAuthenticationConverter(jwtAuthenticationConverter())))
            .exceptionHandling(ex -> ex
                .authenticationEntryPoint(authenticationEntryPoint())
                .accessDeniedHandler(accessDeniedHandler()))
            .build();
    }
    
    @Bean
    public JwtDecoder jwtDecoder() {
        return JwtDecoders.fromIssuerLocation("https://your-auth-server.com");
    }
    
    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(jwt -> {
            Collection<String> authorities = jwt.getClaimAsStringList("authorities");
            return authorities.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        });
        return converter;
    }
}

// Method-level security with SpEL
@Service
public class OrderService {
    
    @PreAuthorize("hasRole('ADMIN') or #customerId == authentication.name")
    public List<Order> getOrdersForCustomer(String customerId) {
        return orderRepository.findByCustomerId(customerId);
    }
    
    @PreAuthorize("@orderSecurityService.canAccessOrder(#orderId, authentication)")
    public Order getOrder(OrderId orderId) {
        return orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFoundException(orderId));
    }
    
    @PostAuthorize("@orderSecurityService.filterSensitiveData(returnObject, authentication)")
    public Order getOrderWithFiltering(OrderId orderId) {
        return orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFoundException(orderId));
    }
}

// Custom security service
@Component
public class OrderSecurityService {
    
    public boolean canAccessOrder(OrderId orderId, Authentication auth) {
        if (hasRole(auth, "ADMIN")) {
            return true;
        }
        
        Order order = orderRepository.findById(orderId).orElse(null);
        return order != null && order.getCustomerId().equals(auth.getName());
    }
    
    public Order filterSensitiveData(Order order, Authentication auth) {
        if (hasRole(auth, "ADMIN")) {
            return order; // Admins see everything
        }
        
        // Filter sensitive data for regular users
        return order.toBuilder()
            .paymentDetails(null) // Hide payment details
            .internalNotes(null)  // Hide internal notes
            .build();
    }
    
    private boolean hasRole(Authentication auth, String role) {
        return auth.getAuthorities().stream()
            .anyMatch(grantedAuthority -> 
                grantedAuthority.getAuthority().equals("ROLE_" + role));
    }
}

Data Protection and Encryption

// Field-level encryption
@Component
public class FieldEncryption {
    
    private final AESUtil aesUtil;
    
    @EventListener
    public void encryptSensitiveFields(BeforeSaveEvent<OrderEntity> event) {
        OrderEntity order = event.getSource();
        
        // Encrypt PII fields
        if (order.getCustomerEmail() != null) {
            order.setCustomerEmail(aesUtil.encrypt(order.getCustomerEmail()));
        }
        
        if (order.getBillingAddress() != null) {
            order.setBillingAddress(aesUtil.encrypt(order.getBillingAddress()));
        }
    }
    
    @EventListener
    public void decryptSensitiveFields(AfterLoadEvent<OrderEntity> event) {
        OrderEntity order = event.getSource();
        
        // Decrypt PII fields
        if (order.getCustomerEmail() != null) {
            order.setCustomerEmail(aesUtil.decrypt(order.getCustomerEmail()));
        }
        
        if (order.getBillingAddress() != null) {
            order.setBillingAddress(aesUtil.decrypt(order.getBillingAddress()));
        }
    }
}

// Secure configuration management
@ConfigurationProperties(prefix = "app.security")
@ConstructorBinding
public record SecurityConfig(
    @Valid EncryptionConfig encryption,
    @Valid JwtConfig jwt,
    @Valid ApiSecurityConfig api
) {
    
    public record EncryptionConfig(
        @NotBlank String algorithm,
        @NotBlank String keyId,
        int keyRotationDays
    ) {}
    
    public record JwtConfig(
        @NotBlank String issuer,
        Duration accessTokenExpiry,
        Duration refreshTokenExpiry
    ) {}
    
    public record ApiSecurityConfig(
        List<String> allowedOrigins,
        int rateLimitPerMinute,
        boolean enableCsrfProtection
    ) {}
}

// Audit logging
@Aspect
@Component
public class SecurityAuditAspect {
    
    private final AuditEventRepository auditEventRepository;
    
    @AfterReturning(pointcut = "@annotation(PreAuthorize)", returning = "result")
    public void auditSecurityOperation(JoinPoint joinPoint, Object result) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        
        AuditEvent auditEvent = AuditEvent.builder()
            .timestamp(Instant.now())
            .userId(auth.getName())
            .operation(joinPoint.getSignature().getName())
            .resource(extractResource(joinPoint))
            .outcome(AuditOutcome.SUCCESS)
            .details(buildAuditDetails(joinPoint, result))
            .build();
            
        auditEventRepository.save(auditEvent);
    }
    
    @AfterThrowing(pointcut = "@annotation(PreAuthorize)", throwing = "ex")
    public void auditSecurityFailure(JoinPoint joinPoint, Exception ex) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        
        AuditEvent auditEvent = AuditEvent.builder()
            .timestamp(Instant.now())
            .userId(auth != null ? auth.getName() : "anonymous")
            .operation(joinPoint.getSignature().getName())
            .resource(extractResource(joinPoint))
            .outcome(AuditOutcome.FAILURE)
            .errorMessage(ex.getMessage())
            .build();
            
        auditEventRepository.save(auditEvent);
    }
}

Observability: Production Insights

Distributed Tracing

// Manual instrumentation with OpenTelemetry
@Component
public class OrderService {
    
    private final Tracer tracer;
    private final OrderRepository orderRepository;
    private final PaymentService paymentService;
    
    public Order createOrder(CreateOrderCommand command) {
        Span span = tracer.spanBuilder("OrderService.createOrder")
            .setSpanKind(SpanKind.INTERNAL)
            .setAttribute("order.customerId", command.getCustomerId())
            .setAttribute("order.itemCount", command.getItems().size())
            .startSpan();
            
        try (Scope scope = span.makeCurrent()) {
            // Add custom events
            span.addEvent("validation.started");
            validateOrder(command);
            span.addEvent("validation.completed");
            
            // Create nested span for database operation
            Order order;
            try (Scope dbScope = createDatabaseSpan("order.save").makeCurrent()) {
                order = orderRepository.save(Order.create(command));
            }
            
            // Async operation with context propagation
            CompletableFuture.supplyAsync(() -> {
                try (Scope asyncScope = span.makeCurrent()) {
                    return paymentService.authorizePayment(command.getPaymentDetails());
                }
            }, Context.taskWrapping(Executors.newCachedThreadPool()));
            
            span.setStatus(StatusCode.OK);
            return order;
            
        } catch (Exception e) {
            span.recordException(e);
            span.setStatus(StatusCode.ERROR, e.getMessage());
            throw e;
        } finally {
            span.end();
        }
    }
    
    private Span createDatabaseSpan(String operation) {
        return tracer.spanBuilder("db.operation")
            .setSpanKind(SpanKind.CLIENT)
            .setAttribute("db.system", "postgresql")
            .setAttribute("db.operation", operation)
            .startSpan();
    }
}

// Automatic instrumentation configuration
@Configuration
public class ObservabilityConfig {
    
    @Bean
    public OpenTelemetry openTelemetry() {
        return OpenTelemetrySdk.builder()
            .setTracerProvider(
                SdkTracerProvider.builder()
                    .addSpanProcessor(BatchSpanProcessor.builder(
                        OtlpGrpcSpanExporter.builder()
                            .setEndpoint("http://jaeger:14250")
                            .build())
                        .build())
                    .setResource(Resource.getDefault()
                        .merge(Resource.builder()
                            .put(ResourceAttributes.SERVICE_NAME, "order-service")
                            .put(ResourceAttributes.SERVICE_VERSION, "1.0.0")
                            .build()))
                    .build())
            .buildAndRegisterGlobal();
    }
    
    @Bean
    public Tracer tracer(OpenTelemetry openTelemetry) {
        return openTelemetry.getTracer("order-service");
    }
}

Metrics and Monitoring

// Custom metrics with Micrometer
@Component
public class OrderMetrics {
    
    private final Counter orderCreatedCounter;
    private final Timer orderProcessingTimer;
    private final Gauge activeOrdersGauge;
    private final DistributionSummary orderValueSummary;
    
    public OrderMetrics(MeterRegistry meterRegistry) {
        this.orderCreatedCounter = Counter.builder("orders.created")
            .description("Number of orders created")
            .tag("service", "order-service")
            .register(meterRegistry);
            
        this.orderProcessingTimer = Timer.builder("orders.processing.duration")
            .description("Time taken to process orders")
            .register(meterRegistry);
            
        this.activeOrdersGauge = Gauge.builder("orders.active")
            .description("Number of active orders")
            .register(meterRegistry, this, OrderMetrics::getActiveOrderCount);
            
        this.orderValueSummary = DistributionSummary.builder("orders.value")
            .description("Distribution of order values")
            .baseUnit("USD")
            .register(meterRegistry);
    }
    
    public void recordOrderCreated(Order order) {
        orderCreatedCounter.increment(
            Tags.of(
                "customer.type", order.getCustomerType(),
                "order.channel", order.getChannel()
            )
        );
        
        orderValueSummary.record(order.getTotalAmount().doubleValue());
    }
    
    public Timer.Sample startOrderProcessing() {
        return Timer.start(orderProcessingTimer);
    }
    
    private double getActiveOrderCount() {
        return orderRepository.countByStatus(OrderStatus.PROCESSING);
    }
}

// Health checks
@Component
public class OrderServiceHealthIndicator implements HealthIndicator {
    
    private final OrderRepository orderRepository;
    private final PaymentServiceClient paymentServiceClient;
    
    @Override
    public Health health() {
        try {
            // Check database connectivity
            long orderCount = orderRepository.count();
            
            // Check external service connectivity
            boolean paymentServiceHealthy = paymentServiceClient.healthCheck();
            
            if (paymentServiceHealthy) {
                return Health.up()
                    .withDetail("database.orders", orderCount)
                    .withDetail("payment.service", "UP")
                    .withDetail("timestamp", Instant.now())
                    .build();
            } else {
                return Health.down()
                    .withDetail("payment.service", "DOWN")
                    .withDetail("timestamp", Instant.now())
                    .build();
            }
            
        } catch (Exception e) {
            return Health.down()
                .withException(e)
                .withDetail("timestamp", Instant.now())
                .build();
        }
    }
}

Logging Strategy

// Structured logging with correlation IDs
@Component
public class CorrelationInterceptor implements HandlerInterceptor {
    
    private static final String CORRELATION_ID_HEADER = "X-Correlation-ID";
    private static final String CORRELATION_ID_MDC_KEY = "correlationId";
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) {
        
        String correlationId = request.getHeader(CORRELATION_ID_HEADER);
        if (correlationId == null) {
            correlationId = UUID.randomUUID().toString();
        }
        
        MDC.put(CORRELATION_ID_MDC_KEY, correlationId);
        response.setHeader(CORRELATION_ID_HEADER, correlationId);
        
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, 
                              HttpServletResponse response, 
                              Object handler, Exception ex) {
        MDC.clear();
    }
}

// Business event logging
@Component
public class BusinessEventLogger {
    
    private final Logger businessLogger = LoggerFactory.getLogger("BUSINESS_EVENTS");
    private final ObjectMapper objectMapper;
    
    @EventListener
    public void logOrderCreated(OrderCreatedEvent event) {
        BusinessEvent businessEvent = BusinessEvent.builder()
            .eventType("ORDER_CREATED")
            .aggregateId(event.orderId().toString())
            .userId(event.customerId())
            .timestamp(event.occurredAt())
            .details(Map.of(
                "itemCount", event.items().size(),
                "totalAmount", event.totalAmount().toString(),
                "channel", event.channel()
            ))
            .build();
            
        businessLogger.info("Business event: {}", 
            objectMapper.writeValueAsString(businessEvent));
    }
    
    @EventListener
    public void logPaymentProcessed(PaymentProcessedEvent event) {
        BusinessEvent businessEvent = BusinessEvent.builder()
            .eventType("PAYMENT_PROCESSED")
            .aggregateId(event.orderId().toString())
            .userId(event.customerId())
            .timestamp(event.occurredAt())
            .details(Map.of(
                "amount", event.amount().toString(),
                "paymentMethod", event.paymentMethod(),
                "transactionId", event.transactionId()
            ))
            .build();
            
        businessLogger.info("Business event: {}", 
            objectMapper.writeValueAsString(businessEvent));
    }
}

Conclusion: Production-Ready Implementation

The implementation details covered in this part form the foundation for production-ready systems:

  • API Design: Contract-first development with proper versioning and multiple communication protocols
  • Data Layer: Multi-model architecture with CQRS, event sourcing, and distributed transaction patterns
  • Security: Defense-in-depth with authentication, authorization, encryption, and comprehensive auditing
  • Observability: Complete visibility through distributed tracing, metrics, health checks, and structured logging

These patterns work together to create systems that are not just functional, but observable, secure, maintainable, and scalable.

In Part 4, we'll focus on performance optimization and scalability patterns: reactive programming, caching strategies, load handling, and scaling patterns that enable your implementation to handle production loads.

Coming Next:

  • Reactive programming with Spring WebFlux
  • Caching strategies and cache consistency
  • Performance optimization techniques
  • Horizontal and vertical scaling patterns

This is Part 3 of "The Complete Guide to Modern Java Architecture." Continue with [Part 4: Performance & Scalability] for optimization strategies and scaling patterns.

Download the companion code examples and architecture templates at: GitHub Repository