Recent Posts
Archives

Archive for the ‘General’ Category

PostHeaderIcon Understanding volatile in Java: A Deep Dive with a Cloud-Native Use Case

In the modern cloud-native world, concurrency is no longer a niche concern. Whether you’re building scalable microservices in Kubernetes, deploying serverless functions in AWS Lambda, or writing multithreaded backend services in Java, thread safety is a concept you must understand deeply.

Among Java’s many concurrency tools, the volatile keyword stands out as both simple and powerful—yet often misunderstood.

This article provides a comprehensive look at volatile, including real-world cloud-based scenarios, a complete Java example, and important caveats every developer should know.

What Does volatile Mean in Java?

At its core, the volatile keyword in Java is used to ensure visibility of changes to variables across threads.

  • Guarantees read/write operations are done directly from and to main memory, avoiding local CPU/thread caches.
  • Ensures a “happens-before” relationship, meaning changes to a volatile variable by one thread are visible to all other threads that read it afterward.

❌ The Problem volatile Solves

Let’s consider the classic issue: Thread A updates a variable, but Thread B doesn’t see it due to caching.

public class ServerStatus {
    private static boolean isRunning = true;

    public static void main(String[] args) throws InterruptedException {
        Thread monitor = new Thread(() -> {
            while (isRunning) {
                // still running...
            }
            System.out.println("Service stopped.");
        });

        monitor.start();
        Thread.sleep(1000);
        isRunning = false;
    }
}

Under certain JVM optimizations, Thread B might never see the change, causing an infinite loop.

✅ Using volatile to Fix the Visibility Issue

public class ServerStatus {
    private static volatile boolean isRunning = true;

    public static void main(String[] args) throws InterruptedException {
        Thread monitor = new Thread(() -> {
            while (isRunning) {
                // monitor
            }
            System.out.println("Service stopped.");
        });

        monitor.start();
        Thread.sleep(1000);
        isRunning = false;
    }
}

This change ensures all threads read the latest value of isRunning from main memory.

☁️ Cloud-Native Use Case: Gracefully Stopping a Health Check Monitor

Now let’s ground this with a real-world cloud-native example. Suppose a Spring Boot microservice runs a background thread that polls the health of cloud instances (e.g., EC2 or GCP VMs). On shutdown—triggered by a Kubernetes preStop hook—you want the monitor to exit cleanly.

public class CloudHealthMonitor {

    private static volatile boolean running = true;

    public static void main(String[] args) {
        Thread healthThread = new Thread(() -> {
            while (running) {
                pollHealthCheck();
                sleep(5000);
            }
            System.out.println("Health monitoring terminated.");
        });

        healthThread.start();

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("Shutdown signal received.");
            running = false;
        }));
    }

    private static void pollHealthCheck() {
        System.out.println("Checking instance health...");
    }

    private static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException ignored) {}
    }
}

This approach ensures your application exits gracefully, cleans up properly, and avoids unnecessary errors or alerts in monitoring systems.

⚙️ How volatile Works Behind the Scenes

Java allows compilers and processors to reorder instructions for optimization. This can lead to unexpected results in multithreaded contexts.

volatile introduces memory barriers that prevent instruction reordering and force flushes to/from main memory, maintaining predictable behavior.

Common Misconceptions

  • volatile makes everything thread-safe!” ❌ False. It provides visibility, not atomicity.
  • “Use volatile instead of synchronized Only for simple flags. Use synchronized for compound logic.
  • volatile is faster than synchronized ✅ Often true—but only if used appropriately.

When Should You Use volatile?

✔ Use it for:

  • Flags like running, shutdownRequested
  • Read-mostly config values that are occasionally changed
  • Safe publication in single-writer, multi-reader setups

✘ Avoid for:

  • Atomic counters (use AtomicInteger)
  • Complex inter-thread coordination
  • Compound read-modify-write operations

✅ Summary Table

Feature volatile
Visibility Guarantee ✅ Yes
Atomicity Guarantee ❌ No
Lock-Free ✅ Yes
Use for Flags ✅ Yes
Use for Counters ❌ No
Cloud Relevance ✅ Graceful shutdowns, health checks

Conclusion

In today’s cloud-native Java ecosystem, understanding concurrency is essential. The volatile keyword—though simple—offers a reliable way to ensure thread visibility and safe signaling across threads.

Whether you’re stopping a background process, toggling a configuration flag, or signaling graceful shutdowns, volatile remains an invaluable tool for writing correct, responsive, and cloud-ready code.

What About You?

Have you used volatile in a critical system before? Faced tricky visibility bugs? Share your insights in the comments!

Related Reading

PostHeaderIcon Advanced Encoding in Java, Kotlin, Node.js, and Python

Encoding is essential for handling text, binary data, and secure transmission across applications. Understanding advanced encoding techniques can help prevent data corruption and ensure smooth interoperability across systems. This post explores key encoding challenges and how Java/Kotlin, Node.js, and Python tackle them.


1️⃣ Handling Special Unicode Characters (Emoji, Accents, RTL Text)

Java/Kotlin

Java uses UTF-16 internally, but for external data (JSON, databases, APIs), explicit encoding is required:

String text = "🔧 Café مرحبا";
byte[] utf8Bytes = text.getBytes(StandardCharsets.UTF_8);
String decoded = new String(utf8Bytes, StandardCharsets.UTF_8);
System.out.println(decoded); // 🔧 Café مرحبا

Tip: Always specify StandardCharsets.UTF_8 to avoid platform-dependent defaults.

Node.js

const text = "🔧 Café مرحبا";
const utf8Buffer = Buffer.from(text, 'utf8');
const decoded = utf8Buffer.toString('utf8');
console.log(decoded); // 🔧 Café مرحبا

Tip: Using an incorrect encoding (e.g., latin1) may corrupt characters.

Python

text = "🔧 Café مرحبا"
utf8_bytes = text.encode("utf-8")
decoded = utf8_bytes.decode("utf-8")
print(decoded)  # 🔧 Café مرحبا

Tip: Python 3 handles Unicode by default, but explicit encoding is always recommended.


2️⃣ Encoding Binary Data for Transmission (Base64, Hex, Binary Files)

Java/Kotlin

byte[] data = "Hello World".getBytes(StandardCharsets.UTF_8);
String base64Encoded = Base64.getEncoder().encodeToString(data);
byte[] decoded = Base64.getDecoder().decode(base64Encoded);
System.out.println(new String(decoded, StandardCharsets.UTF_8)); // Hello World

Node.js

const data = Buffer.from("Hello World", 'utf8');
const base64Encoded = data.toString('base64');
const decoded = Buffer.from(base64Encoded, 'base64').toString('utf8');
console.log(decoded); // Hello World

Python

import base64
data = "Hello World".encode("utf-8")
base64_encoded = base64.b64encode(data).decode("utf-8")
decoded = base64.b64decode(base64_encoded).decode("utf-8")
print(decoded)  # Hello World

Tip: Base64 encoding increases data size (~33% overhead), which can be a concern for large files.


3️⃣ Charset Mismatches and Cross-Language Encoding Issues

A file encoded in ISO-8859-1 (Latin-1) may cause garbled text when read using UTF-8.

Java/Kotlin Solution:

byte[] bytes = Files.readAllBytes(Paths.get("file.txt"));
String text = new String(bytes, StandardCharsets.ISO_8859_1);

Node.js Solution:

const fs = require('fs');
const text = fs.readFileSync("file.txt", { encoding: "latin1" });

Python Solution:

with open("file.txt", "r", encoding="ISO-8859-1") as f:
    text = f.read()

Tip: Always specify encoding explicitly when working with external files.


4️⃣ URL Encoding and Decoding

Java/Kotlin

String encoded = URLEncoder.encode("Hello World!", StandardCharsets.UTF_8);
String decoded = URLDecoder.decode(encoded, StandardCharsets.UTF_8);

Node.js

const encoded = encodeURIComponent("Hello World!");
const decoded = decodeURIComponent(encoded);

Python

from urllib.parse import quote, unquote
encoded = quote("Hello World!")
decoded = unquote(encoded)

Tip: Use UTF-8 for URL encoding to prevent inconsistencies across different platforms.


Conclusion: Choosing the Right Approach

  • Java/Kotlin: Strong type safety, but requires careful Charset management.
  • Node.js: Web-friendly but depends heavily on Buffer conversions.
  • Python: Simple and concise, though strict type conversions must be managed.

📌 Pro Tip: Always be explicit about encoding when handling external data (APIs, files, databases) to avoid corruption.

 

PostHeaderIcon Mastering DNS Configuration: A, AAAA, CNAME, and Best Practices with OVH

I am currently reorganizing a website of mine, hosted at OVHcloud, and it is worth reminding some concepts and best practices related to DNS.

(disclaimer: I am not part of OVH at all, I express myself as a mere customer)

DNS (Domain Name System) is the backbone of the internet, translating human-friendly domain names into IP addresses that computers understand. Yet, many website owners and IT professionals struggle with its configuration. Let’s break down the essential DNS records—A, AAAA, and CNAME—and illustrate best practices using OVH’s interface.

Key DNS Records Explained

1️⃣ A Record (Address Record)

  • Maps a domain (e.g., example.com) to an IPv4 address (e.g., 192.168.1.1).
  • Best practice: Ensure you update this if your server IP changes.

2️⃣ AAAA Record (IPv6 Address Record)

  • Similar to A records but maps to an IPv6 address (e.g., 2001:db8::1).
  • Best practice: If your hosting provider supports IPv6, use this alongside A records for better future-proofing.

3️⃣ CNAME Record (Canonical Name Record)

  • Points a domain (e.g., blog.example.com) to another domain (example.wordpress.com).
  • Best practice: Use CNAME for aliases but avoid pointing the root domain (example.com) to another domain using CNAME—stick to A/AAAA records.

Configuring DNS Records in OVH

To set up a subdomain (blog.example.com) on OVH:

  1. Log in to your OVH Control Panel.
  2. Navigate to Web Cloud → Domains, then select your domain.
  3. Go to the DNS Zone tab and click Add an entry.
  4. Choose A Record if your blog has a dedicated IPv4, or CNAME if pointing to another domain.
  5. Enter your subdomain (blog) and the corresponding IP or domain.
  6. Save changes and wait for propagation (~24 hours max).

Best Practices for DNS Management

  • Use TTL (Time-To-Live) wisely: Lower values (e.g., 300s) allow faster updates but increase queries to your DNS provider.
  • Keep DNS records minimal: Avoid unnecessary CNAME chains to improve resolution speed.
  • Secure with DNSSEC: If your registrar supports it, enable DNSSEC to prevent DNS spoofing.
  • Regularly review DNS settings: Especially after migrations, new SSL configurations, or changes in hosting.

PostHeaderIcon Efficient Inter-Service Communication with Feign and Spring Cloud in Multi-Instance Microservices

In a world where systems are becoming increasingly distributed and cloud-native, microservices have emerged as the de facto architecture. But as we scale
microservices horizontally—running multiple instances for each service—one of the biggest challenges becomes inter-service communication.

How do we ensure that our services talk to each other reliably, efficiently, and in a way that’s resilient to failures?

Welcome to the world of Feign and Spring Cloud.


The Challenge: Multi-Instance Microservices

Imagine you have a user-service that needs to talk to an order-service, and your order-service runs 5 instances behind a
service registry like Eureka. Hardcoding URLs? That’s brittle. Manual load balancing? Not scalable.

You need:

  • Service discovery to dynamically resolve where to send the request
  • Load balancing across instances
  • Resilience for timeouts, retries, and fallbacks
  • Clean, maintainable code that developers love

The Solution: Feign + Spring Cloud

OpenFeign is a declarative web client. Think of it as a smart HTTP client where you only define interfaces — no more boilerplate REST calls.

When combined with Spring Cloud, Feign becomes a first-class citizen in a dynamic, scalable microservices ecosystem.

✅ Features at a Glance:

  • Declarative REST client
  • Automatic service discovery (Eureka, Consul)
  • Client-side load balancing (Spring Cloud LoadBalancer)
  • Integration with Resilience4j for circuit breaking
  • Easy integration with Spring Boot config and observability tools

Step-by-Step Setup

1. Add Dependencies

[xml][/xml]

If using Eureka:

[xml][/xml]


2. Enable Feign Clients

In your main Spring Boot application class:

[java]@SpringBootApplication
@EnableFeignClients
public <span>class <span>UserServiceApplication { … }
[/java]


3. Define Your Feign Interface

[java]
@FeignClient(name = "order-service")
public interface OrderClient { @GetMapping("/orders/{id}")
OrderDTO getOrder(@PathVariable("id") Long id); }
[/java]

Spring will automatically:

  • Register this as a bean
  • Resolve order-service from Eureka
  • Load-balance across all its instances

4. Add Resilience with Fallbacks

You can configure a fallback to handle failures gracefully:

[java]

@FeignClient(name = "order-service", fallback = OrderClientFallback.class)
public interface OrderClient {
@GetMapping("/orders/{id}") OrderDTO getOrder(@PathVariable Long id);
}[/java]

The fallback:

[java]

@Component
public class OrderClientFallback implements OrderClient {
@Override public OrderDTO getOrder(Long id) {
return new OrderDTO(id, "Fallback Order", LocalDate.now());
}
}[/java]


⚙️ Configuration Tweaks

Customize Feign timeouts in application.yml:

[yml]

feign:

    client:

       config:

           default:

                connectTimeout:3000

                readTimeout:500

[/yml]

Enable retry:

[xml]
feign:
client:
config:
default:
retryer:
maxAttempts: 3
period: 1000
maxPeriod: 2000
[/xml]


What Happens Behind the Scenes?

When user-service calls order-service:

  1. Spring Cloud uses Eureka to resolve all instances of order-service.
  2. Spring Cloud LoadBalancer picks an instance using round-robin (or your chosen strategy).
  3. Feign sends the HTTP request to that instance.
  4. If it fails, Resilience4j (or your fallback) handles it gracefully.

Observability & Debugging

Use Spring Boot Actuator to expose Feign metrics:

[xml]

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency[/xml]

And tools like Spring Cloud Sleuth + Zipkin for distributed tracing across Feign calls.


Beyond the Basics

To go even further:

  • Integrate with Spring Cloud Gateway for API routing and external access.
  • Use Spring Cloud Config Server to centralize configuration across environments.
  • Secure Feign calls with OAuth2 via Spring Security and OpenID Connect.

✨ Final Thoughts

Using Feign with Spring Cloud transforms service-to-service communication from a tedious, error-prone task into a clean, scalable, and cloud-native solution.
Whether you’re scaling services across zones or deploying in Kubernetes, Feign ensures your services communicate intelligently and resiliently.

PostHeaderIcon Problem: Spring JMS MessageListener Stuck / Not Receiving Messages

Scenario

A Spring Boot application using ActiveMQ with @JmsListener suddenly stops receiving messages after running for a while. No errors in logs, and the queue keeps growing, but the consumers seem idle.

Setup

@JmsListener(destination = "myQueue", concurrency = "5-10") public void processMessage(String message) { log.info("Received: {}", message); }
  • ActiveMQConnectionFactory was used.

  • The queue (myQueue) was filling up.

  • Restarting the app temporarily fixed the issue.


Investigation

  1. Checked ActiveMQ Monitoring (Web Console)

    • Messages were enqueued but not dequeued.

    • Consumers were still active, but not processing.

  2. Thread Dump Analysis

    • Found that listener threads were stuck in a waiting state.

    • The problem only occurred under high load.

  3. Checked JMS Acknowledgment Mode

    • Default AUTO_ACKNOWLEDGE was used.

    • Suspected an issue with message acknowledgment.

  4. Enabled Debug Logging

    • Added:

      logging.level.org.springframework.jms=DEBUG
    • Found repeated logs like:

      JmsListenerEndpointContainer#0-1 received message, but no further processing
    • This hinted at connection issues.

  5. Tested with a Different Message Broker

    • Using Artemis JMS instead of ActiveMQ resolved the issue.

    • Indicated that it was broker-specific.


Root Cause

ActiveMQ’s TCP connection was silently dropped, but the JMS client did not detect it.

  • When the connection is lost, DefaultMessageListenerContainer doesn’t always recover properly.

  • ActiveMQ does not always notify clients of broken connections.

  • No exceptions were thrown because the connection was technically “alive” but non-functional.


Fix

  1. Enabled keepAlive in ActiveMQ connection

    ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(); factory.setUseKeepAlive(true); factory.setOptimizeAcknowledge(true); return factory;
  2. Forced Reconnection with Exception Listener

    • Implemented:

      factory.setExceptionListener(exception -> { log.error("JMS Exception occurred, reconnecting...", exception); restartJmsListener(); });
    • This ensured that if a connection was dropped, the listener restarted.

  3. Switched to DefaultJmsListenerContainerFactory with DMLC

    • SimpleMessageListenerContainer was less reliable in handling reconnections.

    • New Configuration:

      @Bean public DefaultJmsListenerContainerFactory jmsListenerContainerFactory( ConnectionFactory connectionFactory) { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); factory.setSessionTransacted(true); factory.setErrorHandler(t -> log.error("JMS Listener error", t)); return factory; }

Final Outcome

✅ After applying these fixes, the issue never reoccurred.
🚀 The app remained stable even under high load.


Key Takeaways

  • Silent disconnections in ActiveMQ can cause message listeners to hang.

  • Enable keepAlive and optimizeAcknowledge for reliable connections.

  • Use DefaultJmsListenerContainerFactory with DMLC instead of SMLC.

  • Implement an ExceptionListener to restart the JMS connection if necessary.

 

PostHeaderIcon How to Bypass Elasticsearch’s 10,000-Result Limit with the Scroll API

If you’ve ever worked with the Elasticsearch API, you’ve likely run into its infamous 10,000-result limit. It’s a default cap that can feel like a brick wall when you’re dealing with large datasets—think log analysis, report generation, or bulk data exports. Fortunately, there’s a slick workaround: the Scroll API. In this post, I’ll walk you through why this limit exists, how the Scroll API solves it, and share practical examples to get you started.

Why the 10,000-Result Limit Exists

Elasticsearch caps standard search results at 10,000 to protect performance. Fetching millions of records in one shot with from and size parameters can strain memory and slow things down. But what if you need all that data? That’s where the Scroll API shines—it’s designed for deep pagination, letting you retrieve everything in manageable chunks.

What Is the Scroll API?

Unlike a typical search, the Scroll API maintains a temporary “scroll context” on the server. You grab a batch of results, get a scroll_id, and use it to fetch the next batch—no need to rerun your query. It’s efficient, scalable, and perfect for big data tasks.

How to Use the Scroll API: Step by Step

Let’s break it down with examples you can try yourself.

Step 1: Start the Scroll

Kick things off with a search request. Add the scroll parameter (like 1m for a 1-minute timeout) and set size to control your batch size. Here’s a basic example:
GET /my_index/_search?scroll=1m
{
  "size": 1000,
  "query": {
    "match_all": {}
  }
}
This pulls the first 1,000 hits and returns a `scroll_id`—a long, encoded string you’ll need for the next step.

Step 2: Fetch More Results

Using that `scroll_id`, request the next batch. You don’t need to repeat the query—just send the ID and timeout:
POST /_search/scroll
{
  "scroll": "1m",
  "scroll_id": "c2NhbjsxMDAwO...YOUR_SCROLL_ID_HERE..."
}
Loop this call until you’ve retrieved all your data. Each response includes a new `scroll_id` (sometimes the same, depending on the version), so keep updating it.

Step 3: Clean Up

When you’re done, delete the scroll context to free up server resources. It’s a small but critical step:
DELETE /_search/scroll/c2NhbjsxMDAwO...YOUR_SCROLL_ID_HERE...

Skip this, and you’ll leave dangling contexts that could bog down your cluster.

A Real-World Example

Let’s say you’re sifting through millions of logs for a specific error. Here’s a targeted scroll query:
GET /logs/_search?scroll=2m
{
  "size": 500,
  "query": {
    "match": {
      "error_message": "timeout"
    }
  }
}

Then, use the Scroll API to paginate through every matching log entry. It’s way cleaner than hacking around with `from` and `size`.
Tips for Scroll API Success
  • Batch Size: Stick to a `size` like 500–1000. Too large, and you’ll strain memory; too small, and you’ll make too many requests.
  • Timeout Tuning: Set the scroll duration (e.g., `1m`, `5m`) based on how fast your script processes each batch. Too short, and the context expires mid-run.
  • Automation: Use a script to handle the loop. Python’s `elasticsearch` library, for instance, has a handy scroll helper:
from elasticsearch import Elasticsearch

es = Elasticsearch(["http://localhost:9200"])
scroll = es.search(index="logs", scroll="2m", size=500, body={"query": {"match": {"error_message": "timeout"}}})
scroll_id = scroll["_scroll_id"]

while len(scroll["hits"]["hits"]):
    print(scroll["hits"]["hits"])  # Process this batch
    scroll = es.scroll(scroll_id=scroll_id, scroll="2m")
    scroll_id = scroll["_scroll_id"]

es.clear_scroll(scroll_id=scroll_id)  # Cleanup

Why Scroll Beats the Alternatives

You could tweak `index.max_result_window` to raise the limit, but that’s a performance gamble. Export tools or aggregations might work for summaries, but for raw data retrieval, Scroll is king—efficient and built for the job.

Conclusion

The Scroll API has been a game-changer for my Elasticsearch projects, especially when wrestling with massive indices. It’s simple once you get the hang of it, and the payoff is huge.

PostHeaderIcon Elastic APM: When to Use @CaptureSpan vs. @CaptureTransaction?

If you’re working with Elastic APM in a Java application, you might wonder when to use `@CaptureSpan` versus `@CaptureTransaction`. Both are powerful tools for observability, but they serve different purposes.
🔹 `@CaptureTransaction`:
Use this at the entry point of a request, typically at a controller, service method, or a background job. It defines the start of a transaction and allows you to trace how a request propagates through your system.
🔹 `@CaptureSpan`:
Use this to track sub-operations within a transaction, such as database queries, HTTP calls, or specific business logic. It helps break down execution time and pinpoint performance bottlenecks inside a transaction.

📌 Best Practices:

✅ Apply @CaptureTransaction at the highest-level method handling a request.
✅ Use @CaptureSpan for key internal operations you want to monitor.
✅ Avoid excessive spans—instrument only critical code paths to reduce overhead.

By balancing these annotations effectively, you can get detailed insights into your app’s performance while keeping APM overhead minimal.

 

PostHeaderIcon Java’s Emerging Role in AI and Machine Learning: Bridging the Gap to Production

While Python dominates in model training, Java is becoming increasingly vital for deploying and serving AI/ML models in production. Its performance, stability, and enterprise integration capabilities make it a strong contender.

Java Example: Real-time Object Detection with DL4J and OpenCV

[java]
import …

public class ObjectDetection {

public static void main(String[] args) {
String modelPath = "yolov3.weights";
String configPath = "yolov3.cfg";
String imagePath = "image.jpg";
Net net = Dnn.readNet(modelPath, configPath);
Mat image = imread(imagePath);
Mat blob = Dnn.blobFromImage(image, 1 / 255.0, new Size(416, 416), new Scalar(0, 0, 0), true, false);

net.setInput(blob);

MatVector detections = net.forward(); // Inference

// Process detections (bounding boxes, classes, confidence)
// … (complex logic for object detection results)
// Draw bounding boxes on the image
// … (OpenCV drawing functions)
imwrite("detected_objects.jpg", image);
}
}

[/java]

Python Example: Similar Object Detection with OpenCV and YOLO

[python]

import numpy as np

net = cv2.dnn.readNet("yolov3.weights", "yolov3.cfg")
image = cv2.imread("image.jpg")
blob = cv2.dnn.blobFromImage(image, 1/255.0, (416, 416), swapRB=True, crop=False)
net.setInput(blob)
detections = net.forward()

# Process detections (bounding boxes, classes, confidence)
# … (simpler logic, NumPy arrays)
# Draw bounding boxes on the image
# … (OpenCV drawing functions)
cv2.imwrite("detected_objects.jpg", image)
[/python]

Comparison and Insights:

  • Syntax and Readability: Python’s syntax is generally more concise and readable for data science and AI tasks. Java, while more verbose, offers strong typing and better performance for production deployments.
  • Library Ecosystem: Python’s ecosystem (NumPy, OpenCV, TensorFlow, PyTorch) is more mature and developer-friendly for AI/ML development. Java, with libraries like DL4J, is catching up, but its strength lies in enterprise integration and performance.
  • Performance: Java’s performance is often superior to Python’s, especially for real-time inference and high-throughput applications.
  • Enterprise Integration: Java’s ability to seamlessly integrate with existing enterprise systems (databases, message queues, APIs) is a significant advantage.
  • Deployment: Java’s deployment capabilities are more robust, making it suitable for mission-critical AI applications.

Key Takeaways:

  • Python is excellent for rapid prototyping and model training.
  • Java excels in deploying and serving AI/ML models in production environments, where performance and reliability are paramount.
  • The choice between Java and Python depends on the specific use case and requirements.

PostHeaderIcon CTO Perspective: Choosing a Tech Stack for Mainframe Rebuild

Original post

From LinkedIn: https://www.linkedin.com/posts/matthias-patzak_cto-technology-activity-7312449287647375360-ogNg?utm_source=share&utm_medium=member_desktop&rcm=ACoAAAAWqBcBNS5uEX9jPi1JPdGxlnWwMBjXwaw

Summary of the question

As CTO for a mainframe rebuild (core banking/insurance/retail app, 100 teams/1000 people with Cobol expertise), considering Java/Kotlin, TypeScript/Node.js, Go, and Python. Key decision criteria are technical maturity/stability, robust community, and innovation/adoption. The CTO finds these criteria sound and seeks a language recommendation.

TL;DR: my response

  • Team, mainframe rebuild: Java/Kotlin are frontrunners due to maturity, ecosystem, and team’s Java-adjacent skills. Go has niche potential. TypeScript/Node.js and Python less ideal for core.
  • Focus now: deep PoC comparing Java (Spring Boot) vs. Kotlin on our use cases. Evaluate developer productivity, readability, interoperability, performance.
  • Develop comprehensive Java/Kotlin training for our 100 Cobol-experienced teams.
  • Strategic adoption plan (Java, Kotlin, or hybrid) based on PoC and team input is next.
  • This balances proven stability with modern practices on the JVM for our core.

My detailed opinion

As a CTO with experience in these large-scale transformations, my priority remains a solution that balances technical strength with the pragmatic realities of our team’s current expertise and long-term maintainability.

While Go offers compelling performance characteristics, the specific demands of our core business application – be it in banking, insurance, or retail – often prioritize a mature ecosystem, robust enterprise patterns, and a more gradual transition path for our significant team. Given our 100 teams deeply skilled in Cobol, the learning curve and the availability of readily transferable concepts become key considerations.

Therefore, while acknowledging Go’s strengths in certain cloud-native scenarios, I want to emphasize the strategic advantages of the Java/Kotlin ecosystem for our primary language choice, with a deliberate hesitation and deeper exploration between these two JVM-based options.

Re-emphasizing Java and Exploring Kotlin More Deeply:

  • Java’s Enduring Strength: Java’s decades of proven stability in building mission-critical enterprise systems cannot be overstated. The JVM’s resilience, the vast array of mature libraries and frameworks (especially Spring Boot), and the well-established architectural patterns provide a solid and predictable foundation. Moreover, the sheer size of the Java developer community ensures a deep pool of talent and readily available support for our teams as they transition. For a core system in a regulated industry, this level of established maturity significantly mitigates risk.

  • Kotlin’s Modern Edge and Interoperability: Kotlin presents a compelling evolution on the JVM. Its modern syntax, null safety features, and concise code can lead to increased developer productivity and reduced boilerplate – benefits I’ve witnessed firsthand in JVM-based projects. Crucially, Kotlin’s seamless interoperability with Java is a major strategic advantage. It allows us to:

    • Gradually adopt Kotlin: Teams can start by integrating Kotlin into existing Java codebases, allowing for a phased learning process without a complete overhaul.
    • Leverage the entire Java ecosystem: Kotlin developers can effortlessly use any Java library or framework, giving us access to the vast resources of the Java world.
    • Attract modern talent: Kotlin’s growing popularity can help us attract developers who are excited about working with a modern, yet stable, language on a proven platform.

Why Hesitate Between Java and Kotlin?

The decision of whether to primarily adopt Java or Kotlin (or a strategic mix) requires careful consideration of our team’s specific needs and the long-term vision:

  • Learning Curve: While Kotlin is designed to be approachable for Java developers, there is still a learning curve associated with its new syntax and features. We need to assess how quickly our large Cobol-experienced team can become proficient in Kotlin.
  • Team Preference and Buy-in: Understanding our developers’ preferences and ensuring buy-in for the chosen language is crucial for successful adoption.
  • Long-Term Ecosystem Evolution: While both Java and Kotlin have strong futures on the JVM, we need to consider the long-term trends and the level of investment in each language within the enterprise space.
  • Specific Use Cases: Certain parts of our system might benefit more from Kotlin’s conciseness or specific features, while other more established components might initially remain in Java.

Proposed Next Steps (Revised Focus):

  1. Targeted Proof of Concept (PoC) – Deep Dive into Java and Kotlin: Instead of a broad PoC including Go, let’s focus our initial efforts on a detailed comparison of Java (using Spring Boot) and Kotlin on representative use cases from our core business application. This PoC should specifically evaluate:
    • Developer Productivity: How quickly can teams with a Java-adjacent mindset (after initial training) develop and maintain code in both languages?
    • Code Readability and Maintainability: How do the resulting codebases compare in terms of clarity and ease of understanding for a large team?
    • Interoperability Scenarios: How seamlessly can Java and Kotlin code coexist and interact within the same project?
    • Performance Benchmarking: While the JVM provides a solid base, are there noticeable performance differences for our specific workloads?
  2. Comprehensive Training and Upskilling Program: We need to develop a detailed training program that caters to our team’s Cobol background and provides clear pathways for learning both Java and Kotlin. This program should include hands-on exercises and mentorship opportunities.
  3. Strategic Adoption Plan: Based on the PoC results and team feedback, we’ll develop a strategic adoption plan that outlines whether we’ll primarily focus on Java, Kotlin, or a hybrid approach. This plan should consider the long-term maintainability and talent acquisition goals.

While Go remains a valuable technology for specific niches, for the core of our mainframe rebuild, our focus should now be on leveraging the mature and evolving Java/Kotlin ecosystem and strategically determining the optimal path for our large and experienced team. This approach minimizes risk while embracing modern development practices on a proven platform.

PostHeaderIcon 🚀 Mastering Flyway Migrations with Maven

Managing database migrations can be tricky, but Flyway simplifies the process with versioned scripts for schema changes, data updates, and rollbacks.
Here’s a quick guide with useful examples:

✅ 1️⃣ Creating a Table (V1 – Initial migration)
“`
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50)…,
email VARCHAR(100)…,
created_at TIMESTAMP…
);
“`
✅ 2️⃣ Inserting Sample Data (V2)
“`
INSERT INTO users (username, email) VALUES
(‘alice’, ‘alice@example.com‘),
(‘bob’, ‘bob@…’);
“`

✅ 3️⃣ Adding a New Column (V3 – Schema change)
“`
ALTER TABLE users ADD COLUMN last_login TIMESTAMP;
“`

✅ 4️⃣ Renaming a Column (V4)
“`
ALTER TABLE users RENAME COLUMN email TO contact;
“`

♻ Undo Script (U4__Rename_email_to_contact.sql)
“`
ALTER TABLE users RENAME COLUMN contact TO email;
“`

✅ 5️⃣ Deleting a Column (V5)
“`
ALTER TABLE users DROP COLUMN last_login;
“`

♻ Undo Script (U5__Revert_remove_last_login.sql)
“`
ALTER TABLE users ADD COLUMN last_login TIMESTAMP;
“`

✅ 6️⃣ Deleting Specific Data (V6)
“`
DELETE FROM users WHERE username = ‘alice’;
“`

♻ Undo Script (U6__Revert_delete_user.sql)
“`
INSERT INTO users (username, contact) VALUES (‘alice’, ‘alice@example.com‘);
“`

💡 Configuring Flyway in pom.xml
To integrate Flyway into your Spring Boot or Java project, add the following configuration in your `pom.xml`:
“`
<properties>
<flyway.version>11.4.1</flyway.version>
</properties>

<dependencies>
<!– Flyway Core –>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>${flyway.version}</version>
</dependency>

<!– Database Driver (Example: PostgreSQL) –>
<dependency>
“org.postgresql:postgresql:runtime”
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>${flyway.version}</version>
<configuration>
<url>jdbc:postgresql://localhost:5432/mydb</url>
<user>myuser</user>
<password>mypassword</password>
<schemas>public</schemas>
<locations>filesystem:src/main/resources/db/migration</locations>
</configuration>
</plugin>
</plugins>
</build>
“`

📂 Migration scripts should be placed in:
`src/main/resources/db/migration/`
Example:
“`
V1__Create_users_table.sql
V2__Insert_sample_data.sql
V3__Add_last_login_column.sql
“`

💡 Flyway Maven Plugin Commands
👉Apply migrations:
“`mvn flyway:migrate“`
👉Undo the last migration (if `flyway.licenseKey` is provided):
“`mvn flyway:undo“`
👉Check migration status:
“`mvn flyway:info“`
👉Repair migration history:
“`mvn flyway:repair“`
Clean database (⚠Deletes all tables!):
“`mvn flyway:clean“`