DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

DZone Spotlight

Monday, October 13 View All Articles »
Stop React Native Crashes: A Production-Ready Error Handling Guide

Stop React Native Crashes: A Production-Ready Error Handling Guide

By Anujkumarsinh Donvir DZone Core CORE
You are demonstrating your next mobile application idea to investors, and suddenly the application crashes. Now you are clueless whether it crashed due to bad WIFI at the venue, your service is down, or your application itself hit a snag. There are no logs or alerts to tell you what just happened. Mere imagination of this scenario can send any founder or developer into panic and nervousness. Therefore, having robust error handling is critical to modern-day mobile applications. React Native has become an industry standard in building cross-platform native mobile applications. React Native boasts a rich ecosystem of error handling at various levels of architecture. However, often projects lack clear guidelines on how to handle various error scenarios. This results in either overdoing or underdoing error handling in otherwise robust and feature-rich applications. In this article, you will learn how to adopt a three-layer error handling mechanism, which is gaining widespread popularity with major enterprise-level applications built at leading companies. The article will give you a practical guide instead of pure theory, so you are ready to adapt these concepts immediately. Error Types It is important for you to understand core error types to understand layers of error handling in React Native applications. These error categories are explained below: Network errors: Network errors can occur due to service loss, for example, losing cell reception while traveling or WIFI disconnection. These are the most common types of errors a mobile app experiences.Component errors: Component errors can happen due to wrongly implemented React lifecycle hooks, incorrect rendering of JSX, accessing undefined state properties, or infinite render loops. These errors can crash the entire app at once.Runtime errors: These errors are core JavaScript runtime errors like accessing properties on null or undefined values, JSON parsing failures, type mismatches, and unhandled promise rejections. These errors are the toughest to catch as they usually slip through standard testing. Due to the varied nature of these errors, error handling needs to be layered to prevent and recover the application from a crash state. Three Layers of Error Handling Implementing error handling at three critical layers ensures you catch all major errors. These layers are local error handling, component-level error handling, and global error handling. Please note that these layers are not a 1:1 match for each of the above error types; instead, they work together to ensure that you catch all errors safely. Three layers of error handling Layer 1: Local Error Handling Local error handling refers to errors caught and handled within specific functions or code blocks. These errors are predictable and generally recoverable. You can perform several types of local error handling as described ahead: Try-catch: Classic try-catch blocks are particularly useful when working with async operations such as calling external APIs. Please refer to the code below: JavaScript const fetchUserProfile = async (userId) => { try { const response = await fetch(`https://api.example.com/users/${userId}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data; } catch (error) { console.error('Failed to fetch user profile:', error); Alert.alert('Error', 'Unable to load profile. Please try again.'); return null; } }; React query error handling: Async operations can be enhanced using modern constructs like React Query, which, along with standard try-catch level error handling, offers more options such as retrying failed operations. This allows for graceful recovery from an error state. Observe the sample implementation given below. JavaScript import { useQuery, useMutation } from '@tanstack/react-query'; import api from './services/api'; const UserProfile = () => { const { data, error, isError, isLoading } = useQuery({ queryKey: ['user', 'profile'], queryFn: async () => { const response = await api.get('/user/profile'); return response.data; }, retry: 3, retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), onError: (error) => { if (error.code !== 'NETWORK_ERROR') { Alert.alert('Error', 'Failed to load profile'); } }, }); if (isLoading) return <Text>Loading...</Text>; if (isError) return <Text>Error: {error.message}</Text>; return <Text>{data?.name}</Text>; }; Form validation: Form validations at React Native components not only improve the app’s user experience but also improve backend performance by reducing unnecessary API calls. Below is an example for you to refer to: JavaScript import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; const loginSchema = z.object({ email: z.string().email('Invalid email address'), password: z.string().min(6, 'Password must be at least 6 characters'), }); const LoginForm = () => { const { handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(loginSchema), }); const onSubmit = async (data) => { try { await api.post('/auth/login', data); } catch (error) { Alert.alert('Login Failed', error.message); } }; return ( <View> {errors.email && <Text style={styles.error}>{errors.email.message}</Text>} {errors.password && <Text style={styles.error}>{errors.password.message}</Text>} </View> ); }; Layer 2: Component-Level Error Handling Error boundaries catch component-level errors in the child component tree that go uncaught during local error handling. These can include infinite rendering, improper lifecycle management, and accessing undefined properties during rendering inside components. Without an Error Boundary, a single error can crash the entire application, leading to an unpleasant customer experience. There are two major steps involved in creating an error boundary: 1) creating a dedicated error boundary component, and 2) wrapping the entire application code in it as a child. The code below highlights how to create the error boundary component. JavaScript import React from 'react'; import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null, errorInfo: null }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, errorInfo) { console.error('ErrorBoundary caught an error:', error, errorInfo); this.setState({ errorInfo: errorInfo.componentStack, }); } handleReset = () => { this.setState({ hasError: false, error: null, errorInfo: null }); }; render() { if (this.state.hasError) { return ( <View style={styles.container}> <Text style={styles.title}>Oops! Something went wrong</Text> <Text style={styles.message}> We're sorry for the inconvenience. Please try again. </Text> {__DEV__ && this.state.error && ( <View style={styles.errorDetails}> <Text style={styles.errorText}> {this.state.error.toString()} </Text> {this.state.errorInfo && ( <Text style={styles.errorStack}> {this.state.errorInfo} </Text> )} </View> )} <TouchableOpacity style={styles.button} onPress={this.handleReset} > <Text style={styles.buttonText}>Try Again</Text> </TouchableOpacity> </View> ); } return this.props.children; } } Once the error boundary component is created, you can wrap the entire application around it by referring to the code below. JavaScript import React from 'react'; import { SafeAreaView } from 'react-native'; import ErrorBoundary from './components/ErrorBoundary'; import MainNavigator from './navigation/MainNavigator'; const App = () => { return ( <ErrorBoundary> <SafeAreaView style={{ flex: 1 }> <MainNavigator /> </SafeAreaView> </ErrorBoundary> ); }; export default App; Layer 3: Global Error Handling Global error handling is the final safety measure that can help catch uncaught errors in local layer and component layer error handling. These include catching uncaught JavaScript exceptions, unhandled promise rejections, and more. Furthermore, global error handling can help centralize API error handling, creating manageable error handling for entire applications. Global error handling component: Implementation of the global error handling component is quite similar to the error boundary component, with two stages: creating the component and using it in the root application initialization. You can refer to the code below to create a global error handling component. JavaScript import { Alert } from 'react-native'; class GlobalErrorHandler { static init() { this.setupErrorHandler(); this.setupPromiseRejectionHandler(); } static setupErrorHandler() { ErrorUtils.setGlobalHandler((error, isFatal) => { console.error('Global Error Handler:', { error, isFatal, message: error.message, stack: error.stack, }); if (isFatal) { Alert.alert( 'Unexpected Error', 'The app encountered a critical error and needs to restart. ' + 'We apologize for the inconvenience.', [ { text: 'Restart', onPress: () => {}, }, ] ); } else { console.warn('Non-fatal error occurred:', error.message); } }); } static setupPromiseRejectionHandler() { if (__DEV__) { require('promise/setimmediate/rejection-tracking').enable({ allRejections: true, onUnhandled: (id, error) => { console.warn('Unhandled Promise Rejection (Dev):', id, error); }, onHandled: (id) => { console.log('Promise rejection was handled:', id); }, }); } } static handleError(error, context = {}) { console.error('Handled Error:', { error, context, message: error.message, stack: error.stack, }); let userMessage = 'Something went wrong'; if (error.code === 'NETWORK_ERROR') { userMessage = 'No internet connection. Please check your network.'; } else if (error.code === 'UNAUTHORIZED') { userMessage = 'Your session has expired. Please login again.'; } else if (error.message) { userMessage = error.message; } return userMessage; } } export default GlobalErrorHandler; Once created, initialize it in the main application component using the below reference code. JavaScript import React, { useEffect } from 'react'; import { SafeAreaView } from 'react-native'; import ErrorBoundary from './components/ErrorBoundary'; import GlobalErrorHandler from './utils/GlobalErrorHandler'; import MainNavigator from './navigation/MainNavigator'; const App = () => { useEffect(() => { GlobalErrorHandler.init(); }, []); return ( <ErrorBoundary> <SafeAreaView style={{ flex: 1 }> <MainNavigator /> </SafeAreaView> </ErrorBoundary> ); }; export default App; Axios Interceptor: Axios Interceptor acts as a centralized API error handler. You can refer to the code below to implement it. After the implementation, you can import it wherever you need API calling. JavaScript import axios from 'axios'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { Alert } from 'react-native'; const api = axios.create({ baseURL: 'https://api.example.com', timeout: 10000, }); api.interceptors.request.use( async (config) => { const token = await AsyncStorage.getItem('authToken'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error) ); api.interceptors.response.use( (response) => response, async (error) => { if (!error.response) { Alert.alert('Network Error', 'Please check your internet connection'); return Promise.reject({ code: 'NETWORK_ERROR', message: 'No internet connection', }); } switch (error.response.status) { case 401: await AsyncStorage.removeItem('authToken'); return Promise.reject({ code: 'UNAUTHORIZED', message: 'Session expired. Please login again.', }); case 403: Alert.alert('Access Denied', 'You do not have permission'); break; case 404: Alert.alert('Not Found', 'Resource not found'); break; case 500: case 502: case 503: Alert.alert('Server Error', 'Please try again later'); break; } return Promise.reject(error); } ); export default api; Conclusion You have learned about different errors your React Native application can encounter and a layered approach to handling them. This knowledge will help you build robust mobile applications that are reliable and resilient. If you wish to dive deeper, you can explore error logging services like Sentry and Firebase Crashlytics. While these services have costs, they are very useful tools for understanding and fixing production errors. More
Efficiently Reading Large Excel Files (Over 1 Million Rows) Using the Open-Source Sjxlsx Java API

Efficiently Reading Large Excel Files (Over 1 Million Rows) Using the Open-Source Sjxlsx Java API

By Mahendran Chinnaiah
If you are a developer, regardless of the technologies you use, at some point, you will face the challenge of handling large Excel files. Most commonly, you will see the "out of memory" error when handling these files. Here is the requirement: You get a workbook with 100 spreadsheets, and each of them has 1 million records with a minimum of 100 columns. For your reference, here's how many rows each Excel version can support: 1,048,576 rows – Excel 365, 2013, 2010, 200765,536 rows – Excel 2003 and earlier versions Cost and Hosting Server Limitations Third-party license purchase is not feasible. Open source is allowed.Deployment should be on average-tier cloud environments (4 GB disk/4 GB RAM or less) or on-premises Windows Server (16/32 GB RAM) already loaded with 10+ running applications. How would you handle this situation? Let's explore the current digital market. Are there any open-source solutions available to meet this requirement? I can’t use “interop” when your cloud runs on "Linux OS," and it also causes intermittent hang issues in multi-threaded applications. Most of them will say that the popular library is the "Apache POI" streaming library. Let’s proceed with a practical implementation using Apache POI. To get the large dataset (Excel) file, we have N number of websites, such as awesome-public-datasets, Google Dataset Search, and World Bank Data. Personally, I frequently visit Kaggle. Let's download the container Dataset as a 1.9GB CSV file, and then save it as an Excel file (.XSLX), which became around a 600 MB XLSX file. The sheet includes 87 columns and 1048576 rows. Before we run the solution, let me capture my laptop's resource usage so that we can compare later. Resource Usage CPU: 3%Memory: 54% IDE I am using Eclipse IDE (v: 4.36.0). Apache POI I am using the latest Apache POI 5.2.5 and other dependency JARS available from Apache POI. Source Code Here, I am just attempting to read the sheet name from the workbook, not the rows. Java public static void main(String[] args) throws Exception { String filePath = "C:\\POC\\Containers_Dataset.xlsx"; ReadExcelbyApachePOI(filePath); } /*List out sheet name*/ static void ReadExcelbyApachePOI(String filePath) throws Exception { try (OPCPackage opcPackage = OPCPackage.open(new File(filePath), PackageAccess.READ)) { XSSFWorkbook workbook = new XSSFWorkbook(opcPackage); XSSFReader xssfReader = new XSSFReader(opcPackage); StylesTable styles = xssfReader.getStylesTable(); XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader .getSheetsData(); while (iter.hasNext()) { InputStream stream = iter.next(); String sheetName = iter.getSheetName(); System.out.println("Sheetname: " + sheetName); } } catch (IOException e) { e.printStackTrace(); } } Result Encountering "Java heap space - Out of memory error." Java Exception in thread "main" java.lang.OutOfMemoryError: Java heap space Resource Usage CPU: 58%Memory: 94% Let us try with the sjxlsx open-source API. It’s an open-source Java API Source Code; this project was first published on Google Code, and it seems unmaintained. In GitHub, it is available for anyone who can download and update the changes for their needs. "Memory" and "speed" are primary goals of this API. It provides two modes, which are "classic" and "stream." Classic – All records of the sheet will be loaded.Stream – Read one record at a time. Microsoft XLSX uses XML+zip (OOXML) to store the data. So, to be fast, "sjxlsx" uses STAX for XML input and output. Source Code Java public static void main(String[] args) throws Exception { String filePath = "C:\\POC\\Containers_Dataset.xlsx"; SimpleXLSXWorkbook workbook = newWorkbook(filePath); testLoadALL(workbook); } private static SimpleXLSXWorkbook newWorkbook(String filePath) { return new SimpleXLSXWorkbook(new File(filePath)); } /*Read Each Row*/ private static void printRow(int rowPos, com.incesoft.tools.excel.xlsx.Cell[] row) { int cellPos = 0; for (com.incesoft.tools.excel.xlsx.Cell cell : row) { System.out.println(com.incesoft.tools.excel.xlsx.Sheet.getCellId(rowPos, cellPos) + "=" + cell.getValue()); cellPos++; } } /*Load & Read workbook * false => Read each row * true => Load all rows */ public static void testLoadALL(SimpleXLSXWorkbook workbook) { com.incesoft.tools.excel.xlsx.Sheet sheetToRead = workbook.getSheet(0,false); SheetRowReader rowreader = sheetToRead.newReader(); int rowPos = 0; while (rowreader != null) { com.incesoft.tools.excel.xlsx.Cell[] row = rowreader.readRow(); printRow(rowPos, row); rowPos++; } } Resource Usage CPU: 3% (No changes)RAM: 61% (7% usage: 1 GB usage) Output Java BN1048519=40298 BO1048519=0 BP1048519=0 BQ1048519=0 BR1048519=0 BS1048519=610 BT1048519=0 BU1048519=1 BV1048519=64240 BW1048519=923 BX1048519=158 BY1048519=32 BZ1048519=0 CA1048519=0 CB1048519=0 CC1048519=0 CD1048519=0 CE1048519=0 CF1048519=0 CG1048519=0 CH1048519=10000206 CI1048519=0 A1048520=100.64.0.2-10.16.0.9-35919-8080-6 B1048520=100.64.0.2 C1048520=35919 D1048520=10.16.0.9 E1048520=8080 F1048520=6 G1048520=45266.83932053241 H1048520=41626 I1048520=6 J1048520=5 K1048520=515 L1048520=357 M1048520=515 N1048520=0 O1048520=85.8333333333333 P1048520=210.24786958888899 Q1048520=357 R1048520=0 S1048520=71.400000000000006 T1048520=159.65525359348399 U1048520=20948.445682986501 V1048520=264.25791572574798 W1048520=4162.5999999999904 X1048520=12728.124713056101 Y1048520=40374 Z1048520=9 AA1048520=41626 AB1048520=8325.2000000000007 AC1048520=17922.528077813098 AD1048520=40374 AE1048520=29 AF1048520=41594 AG1048520=10398.5 AH1048520=20011.5685292282 AI1048520=40406 AJ1048520=26 AK1048520=1 AL1048520=1 AM1048520=0 AN1048520=0 AO1048520=0 AP1048520=0 AQ1048520=200 AR1048520=168 AS1048520=144.14068130495301 AT1048520=120.11723442079401 AU1048520=0 AV1048520=515 AW1048520=79.272727272727295 AX1048520=179.87445116474399 AY1048520=32354.8181818181 AZ1048520=2 BA1048520=2 BB1048520=0 BC1048520=2 BD1048520=10 BE1048520=0 BF1048520=0 BG1048520=0 BH1048520=0.83333333333333304 BI1048520=79.272727272727195 BJ1048520=85.8333333333333 BK1048520=71.400000000000006 BL1048520=0 BM1048520=0 BN1048520=0 BO1048520=0 BP1048520=0 BQ1048520=0 BR1048520=0 BS1048520=46 BT1048520=0 BU1048520=32 BV1048520=64240 BW1048520=502 BX1048520=1 BY1048520=32 BZ1048520=0 CA1048520=0 CB1048520=0 CC1048520=0 CD1048520=0 CE1048520=0 CF1048520=0 CG1048520=0 CH1048520=41626 CI1048520=0 A1048521=100.64.0.2-10.16.0.9-9479-8080-6 B1048521=100.64.0.2 C1048521=9479 D1048521=10.16.0.9 E1048521=8080 F1048521=6 G1048521=45266.835683206016 H1048521=111205 I1048521=6 J1048521=5 K1048521=537 L1048521=357 Performance Results Woo-hoo! I can read the records. The winner is "sjxlsx." It has been proven that this library consumes less than 1 GB of memory, compared to the higher usage by Apache POI. It is an excellent open-source Java API for reading large Excel datasets. Additional Features In addition to that, it supports writing Excel and offers rich data formatting in sheets. Java public static void main(String[] args) throws Exception { // WRITE - we take WRITE as a special kind of MODIFY SimpleXLSXWorkbook workbook = newWorkbook(); OutputStream output = ExcelOutput("write"); WriteExcel(workbook, output); output.close(); } private static SimpleXLSXWorkbook newWorkbook() { return new SimpleXLSXWorkbook(new File("/sample.xlsx")); } private static OutputStream ExcelOutput(String suffix) throws Exception { return new BufferedOutputStream(new FileOutputStream("/sample_" + suffix + ".xlsx")); } public static void WriteExcel(SimpleXLSXWorkbook workbook, OutputStream outputStream) throws Exception { com.incesoft.tools.excel.xlsx.Sheet sheet = workbook.getSheet(0); WriteRecords(workbook, sheet, 0); workbook.commit(outputStream); } static public void WriteRecords(SimpleXLSXWorkbook wb, com.incesoft.tools.excel.xlsx.Sheet sheet, int rowOffset) { int columnCount = 10; int rowCount = 10; int offset = rowOffset; for (int r = offset; r < offset + rowCount; r++) { int modfiedRowLength = sheet.getModfiedRowLength(); for (int c = 0; c < columnCount; c++) { sheet.modify(modfiedRowLength, c, r + "," + c, null); } } } Writing a Richly Styled Row Java public static void WriteRichStyleRow(SimpleXLSXWorkbook wb, com.incesoft.tools.excel.xlsx.Sheet sheet) throws Exception { Font font = wb.createFont(); font.setColor("FFFF0000"); Fill fill = wb.createFill(); fill.setFgColor("FF00FF00"); CellStyle style = wb.createStyle(font, fill); RichText richText = wb.createRichText(); richText.setText("test_text"); Font font2 = wb.createFont(); font2.setColor("FFFF0000"); richText.applyFont(font2, 1, 2); sheet.modify(0, 0, (String) null, style); sheet.modify(1, 0, richText, null); } Summary Ultimately, "sjxlsx" provides an efficient, lightweight way to read large Excel files without infrastructure headaches. Thank you! More

Trend Report

Kubernetes in the Enterprise

Over a decade in, Kubernetes is the central force in modern application delivery. However, as its adoption has matured, so have its challenges: sprawling toolchains, complex cluster architectures, escalating costs, and the balancing act between developer agility and operational control. Beyond running Kubernetes at scale, organizations must also tackle the cultural and strategic shifts needed to make it work for their teams.As the industry pushes toward more intelligent and integrated operations, platform engineering and internal developer platforms are helping teams address issues like Kubernetes tool sprawl, while AI continues cementing its usefulness for optimizing cluster management, observability, and release pipelines.DZone's 2025 Kubernetes in the Enterprise Trend Report examines the realities of building and running Kubernetes in production today. Our research and expert-written articles explore how teams are streamlining workflows, modernizing legacy systems, and using Kubernetes as the foundation for the next wave of intelligent, scalable applications. Whether you're on your first prod cluster or refining a globally distributed platform, this report delivers the data, perspectives, and practical takeaways you need to meet Kubernetes' demands head-on.

Kubernetes in the Enterprise

Refcard #387

Getting Started With CI/CD Pipeline Security

By Sudip Sengupta DZone Core CORE
Getting Started With CI/CD Pipeline Security

Refcard #216

Java Caching Essentials

By Granville Barnett
Java Caching Essentials

More Articles

*You* Can Shape Trend Reports: Join DZone's Database Systems Research
*You* Can Shape Trend Reports: Join DZone's Database Systems Research

Hey, DZone Community! We have an exciting year of research ahead for our beloved Trend Reports. And once again, we are asking for your insights and expertise (anonymously if you wish) — readers just like you drive the content we cover in our Trend Reports. Check out the details for our research survey below. Database Systems Research With databases powering nearly every modern application nowadays, how are developers and organizations utilizing, managing, and evolving these systems — across usage, architecture, operations, security, and emerging trends like AI and real-time analytics? Take our short research survey (~10 minutes) to contribute to our upcoming Trend Report. Oh, and did we mention that anyone who takes the survey could be one of the lucky four to win an e-gift card of their choosing? We're diving into key topics such as: The databases and query languages developers rely onExperiences and challenges with cloud migrationPractices and tools for data security and observabilityData processing architectures and the role of real-time analyticsEmerging approaches like vector and AI-assisted databases Join the Database Systems Research Over the coming month, we will compile and analyze data from hundreds of respondents; results and observations will be featured in the "Key Research Findings" of our upcoming Trend Report. Your responses help inform the narrative of our Trend Reports, so we truly cannot do this without you. Stay tuned for each report's launch and see how your insights align with the larger DZone Community. We thank you in advance for your help! —The DZone Content and Community team

By DZone Editorial
Building Realistic Test Data in Java: A Hands-On Guide for Developers
Building Realistic Test Data in Java: A Hands-On Guide for Developers

There’s something that every backend or API developer faces sooner or later: the need for good fake data. Whether you’re testing a new API, populating a database for demos, or simply trying to make your unit tests less “boring”, fake data is part of your daily routine. The problem? Most fake data feels… fake. You end up with “John Doe” and “123 Main Street” repeated over and over, which doesn’t look great when showing a prototype to your team or client. So today, let’s fix that. In this article, we’ll explore two powerful Java libraries that make generating fake yet realistic data a breeze: DataFaker and EasyRandom. We’ll go beyond just generating names and emails — we’ll learn how to integrate both libraries inside a Spring Boot 3 project, how to combine their strengths, and how to make everything available through a REST API that returns test data. This isn’t a theoretical overview. We’ll look at real code, and you’ll walk away knowing exactly how to reproduce it in your next project. Why Bother Generating Fake Data? Let’s face it: manually crafting test data is time-consuming and error-prone. Imagine you’re developing a system for managing users. You need to test pagination, filtering, sorting, and edge cases (like missing emails or very long names). Instead of hand-writing 100 lines of sample JSON, wouldn’t it be nicer to generate it automatically and instantly? Good fake data helps you: Validate your logic in a more realistic scenarioShowcase prototypes with data that “looks real”Stress test APIs or UI components with variable inputsAutomate unit tests without boilerplate “mock builders” So instead of hardcoding “Alice” and “Bob,” we’ll let DataFaker and EasyRandom do the heavy lifting. DataFaker: The Modern, Improved JavaFaker If you’ve used JavaFaker in the past, DataFaker is its modern, actively maintained successor. It’s built for recent Java versions (Java 17+), is fast, and offers hundreds of data categories — including names, addresses, finance, company information, internet data, crypto keys, and even Star Wars characters if you feel nostalgic. Let’s see a quick example: Java import net.datafaker.Faker; Faker faker = new Faker(); System.out.println(faker.name().fullName()); System.out.println(faker.internet().emailAddress()); System.out.println(faker.address().fullAddress()); Run that, and you’ll get something like: Plain Text Matilde Marques [email protected] Rua do Carmo 45, 1200-093 Lisboa Pretty cool, right? And it even looks localized if you change the locale. Java Faker faker = new Faker(new Locale("pt")); Now your data fits your language and region — an enjoyable touch for international testing. EasyRandom: Because We Need More Than Fields While DataFaker focuses on realistic field-level data, EasyRandom (formerly Random Beans) takes a different approach. It’s great when you have complex Java objects — like entities or DTOs — and you want them automatically filled with random but valid values. Think of EasyRandom as a smart “object generator” that knows how to populate your classes, including nested objects, lists, and maps. Example: Java import org.jeasy.random.EasyRandom; EasyRandom easyRandom = new EasyRandom(); Person randomPerson = easyRandom.nextObject(Person.class); This will create a fully populated Person instance, with random strings, numbers, and even nested attributes. So, where DataFaker gives realism (e.g., “John Smith, [email protected]”), EasyRandom gives structure and automation (e.g., filling an entire POJO graph). And the best part? You can combine both — letting EasyRandom create your object and then using DataFaker to polish specific fields with more believable data. Combining DataFaker and EasyRandom: The Sweet Spot Here’s where things get fun. We’ll create a small Spring Boot REST API that exposes endpoints to generate fake users. Each user will have an id, fullName, email, phone, and address. We’ll use DataFaker for realism and EasyRandom for automation. Our project structure looks like this: Plain Text src/ ├─ main/java/com/example/fakedata/ │ ├─ Application.java │ ├─ config/ │ ├─ api/ │ ├─ controller/ │ ├─ domain/ │ ├─ dto/ │ ├─ service/ │ └─ mapper/ └─ resources/ └─ static/index.html The User Domain Class We’ll keep it simple, using Lombok to avoid boilerplate: Java @Data @Builder public class User { private String id; private String fullName; private String email; private String phone; private String address; } And for the API responses, we’ll use a Java record for immutability and readability: Java public record UserDto(String id, String fullName, String email, String phone, String address) { } The Service: Combining Both Libraries Here’s the core of our project: Java @Service public class DataGenService { private final Faker faker = new Faker(Locale.ENGLISH); private final EasyRandom easyRandom; public DataGenService() { EasyRandomParameters params = new EasyRandomParameters() .seed(System.currentTimeMillis()) .stringLengthRange(5, 20); this.easyRandom = new EasyRandom(params); } public User randomUserViaDatafaker() { return User.builder() .id(UUID.randomUUID().toString()) .fullName(faker.name().fullName()) .email(faker.internet().emailAddress()) .phone(faker.phoneNumber().cellPhone()) .address(faker.address().fullAddress()) .build(); } public User randomUserViaEasyRandom() { User u = easyRandom.nextObject(User.class); if (u.getId() == null || u.getId().isBlank()) { u.setId(UUID.randomUUID().toString()); } u.setFullName(faker.name().fullName()); u.setEmail(faker.internet().emailAddress()); return u; } public List<User> manyUsers(int count, boolean easyRandomMode) { return IntStream.range(0, count) .mapToObj(i -> easyRandomMode ? randomUserViaEasyRandom() : randomUserViaDatafaker()) .collect(Collectors.toList()); } } You can see how we use DataFaker for realism and EasyRandom for structure — like a two-chef recipe: one creates the base, the other adds seasoning. The REST Controller Now, let’s make it accessible through a REST API. Java @RestController @RequestMapping("/api/users") public class UserController { private final DataGenService service; public UserController(DataGenService service) { this.service = service; } @GetMapping("/{count}") public ApiResponse<List<UserDto>> generateUsers(@PathVariable int count, @RequestParam(defaultValue = "false") boolean easy) { List<UserDto> users = service.manyUsers(count, easy) .stream().map(UserMapper::toDto) .collect(Collectors.toList()); return ApiResponse.of(users); } } And to make our API responses consistent, we wrap everything in an envelope with a timestamp: Java public record ApiResponse<T>(T data, Instant timestamp) { public static <T> ApiResponse<T> of(T data) { return new ApiResponse<>(data, Instant.now()); } } That way, every API call returns data like this: JSON { "data": [ { "id": "e7b1c37a-8b20-43c1-8ff3-b4aef8d89c3a", "fullName": "Lina Cordeiro", "email": "[email protected]", "phone": "+351 912 345 678", "address": "Rua do Comércio 12, Porto" } ], "timestamp": "2025-10-06T13:02:45.321Z" } Much cleaner and easier to debug. Why Timestamp in Responses? Adding timestamps isn’t just for looks. It’s a simple, useful practice that improves observability. When debugging requests in distributed systems or when clients log responses, having the server timestamp right in the payload helps you correlate events — it’s a micro detail with macro benefits. Why Both Libraries Are Better Together You might wonder: “Why not just use DataFaker alone?” Good question. DataFaker is unbeatable for producing realistic values, but it doesn’t automatically populate deep object structures.EasyRandom, on the other hand, is great for object graphs, but its randomness feels too synthetic — you’ll end up with “[email protected].” Together, they give you: Realism + AutomationEase of integration with tests and APIsConsistency through configuration and seeds It’s a bit like combining a random word generator with a translator — one provides variation, the other makes sense of it. Going Further: Postman, Docker, and CI/CD Our complete project also includes: A Postman collection for quick testingA Dockerfile and docker-compose.yml for containerizationGitHub Actions CI and Dependabot setup for automated builds and dependency updates That makes this small demo a production-grade reference project for testing and learning. If you’re mentoring junior developers or building internal utilities, this is a great example to show clean architecture and reproducible data generation. Repo: github.com/wallaceespindola/fake-data-springboot Practical Ideas for Using This Setup Load testing: Generate thousands of fake users to populate a database.UI prototyping: Feed your frontend with realistic API data.Demo environments: Seed a sandbox with dynamic sample users.Unit tests: Replace new User("a","b") with a call to DataGenService.randomUserViaDatafaker().Data anonymization: Quickly replace sensitive production data with fake equivalents. Each of these is a real-world scenario where this combination shines. Closing Thoughts The difference between a “meh” test dataset and a “wow, this looks real!” demo often comes down to how you generate data. With DataFaker and EasyRandom, you can automate that process elegantly — using modern Java, minimal boilerplate, and libraries that just make sense together. You’ll not only save hours when building tests or mock APIs but also deliver demos that feel alive, diverse, and realistic. The best part? It’s all open-source, lightweight, and easy to integrate with Spring Boot, Quarkus, Micronaut, or even a plain Java console app. So next time you need to populate an API or test your system’s resilience, don’t settle for "John Doe" anymore. Give your fake data some personality — and let Java do the heavy lifting. Need more tech insights? Check out my GitHub repo and LinkedIn page. Happy coding!

By Wallace Espindola
AI-Assisted Kubernetes Diagnostics: A Practical Implementation
AI-Assisted Kubernetes Diagnostics: A Practical Implementation

Kubernetes troubleshooting follows a repetitive pattern: identify unhealthy pods, examine descriptions, review logs, analyze events, and correlate information to find root causes. For common issues like CrashLoopBackOff, ImagePullBackOff, or OOMKilled pods, engineers repeat the same diagnostic steps daily, sometimes dozens of times per week in busy production environments. The traditional workflow requires running multiple kubectl commands in sequence, mentally correlating outputs from pod descriptions, container logs, event streams, and resource configurations. An engineer investigating a single failing pod might execute 5–10 commands, read through hundreds of lines of output, and spend 10-30 minutes connecting the dots between symptoms and root causes. For straightforward issues like memory limits or missing images, this time investment yields solutions that follow predictable patterns. Large language models can process this same information — pod descriptions, logs, events — and apply pattern recognition trained on thousands of similar scenarios. Instead of an engineer manually correlating data points, an LLM can analyze the complete context at once and suggest likely root causes with specific remediation steps. This article walks through a proof-of-concept tool available at [opscart/k8s-ai-diagnostics](https://github.com/opscart/k8s-ai-diagnostics). The tool detects unhealthy pods in a namespace, analyzes them using OpenAI GPT-4, and provides diagnostics with suggested remediation steps. For certain failure types like CrashLoopBackOff or OOMKilled, it applies fixes automatically with human approval. The implementation stays minimal — just Python, kubectl, and the OpenAI API — making it easy to deploy and test in existing Kubernetes environments. The Problem Space Manual Diagnostic Overhead When a pod fails in Kubernetes, the diagnostic process typically looks like this: Shell # Check pod status kubectl get pods -n production # Examine pod details kubectl describe pod failing-pod -n production # Review container logs kubectl logs failing-pod -n production # Check previous container logs if crashed kubectl logs failing-pod -n production --previous # Examine events kubectl get events -n production --field-selector involvedObject.name=failing-pod For experienced engineers, this workflow becomes muscle memory. However, it still requires: Context switching between multiple kubectl commandsMental correlation of information across different outputsKnowledge of common failure patterns and their solutionsTime to write and apply remediation patches Common Failure Patterns Kubernetes pods fail in predictable ways: ImagePullBackOff: Wrong image name, missing credentials, or registry connectivity issuesCrashLoopBackOff: Application startup failures, missing dependencies, or configuration errorsOOMKilled: Container memory usage exceeds defined limitsProbe Failures: Readiness or liveness probes fail due to application issues or misconfigurations Each pattern has typical root causes and standard remediation approaches. This repetitive nature makes automation worth exploring. The Solution: LLM-Powered Diagnostics The k8s-ai-diagnostics project implements an agent that: Scans a namespace for unhealthy podsCollects pod descriptions and logs via kubectlSends context to OpenAI GPT-4 for analysisReceives structured diagnostics, including root cause, reasons, and fixesOptionally applies remediation with human approval Architecture The tool uses a simple pipeline: Shell ┌──────────────────┐ │ kubectl CLI │ │ (pod status, │ │ descriptions, │ │ logs) │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ Python Script │ │ - Detect pods │ │ - Collect data │ │ - Build context │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ OpenAI GPT-4 │ │ - Analyze data │ │ - Root cause │ │ - Suggest fixes │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ Remediation │ │ - Human approve │ │ - Apply patches │ │ - kubectl cmds │ └──────────────────┘ The implementation keeps dependencies minimal: Python 3.8+, kubectl, and the OpenAI API. Installation and Setup Prerequisites Shell # Python 3.8 or higher python3 --version # kubectl configured with cluster access kubectl cluster-info # OpenAI API key export OPENAI_API_KEY="your-api-key" Installation Shell # Clone repository git clone https://github.com/opscart/k8s-ai-diagnostics.git cd k8s-ai-diagnostics # Create virtual environment python3 -m venv venv source venv/bin/activate # Install dependencies pip install -r requirements.txt Deploy Test Scenarios Set up local env The repository includes test deployments that simulate common failures: Shell # Create namespace kubectl create namespace ai-apps # Deploy test scenarios sh k8s-manifests/deploy.sh This deploys four intentionally broken pods: broken-nginx: ImagePullBackOff (invalid image name)crashy: CrashLoopBackOff (container exits immediately)oom-test: OOMKilled (exceeds memory limits)unhealthy-probe: Probe failures (missing expected files) Verify deployment: Shell kubectl get pods -n ai-apps Expected output: Shell NAME READY STATUS RESTARTS AGE broken-nginx-5f6cdfb774-m7kw7 0/1 ImagePullBackOff 0 2m crashy-77747bbb47-mr75j 0/1 CrashLoopBackOff 6 2m oom-test-5fd8f6b8d9-c9p52 0/1 OOMKilled 3 2m unhealthy-probe-78d9b76567-5x8h6 0/1 Running 1 2m Running the Diagnostic Agent Execute the agent: Python python3 k8s_ai_agent.py The script prompts for a namespace: Python Enter the namespace to scan: ai-apps Example Diagnostic Session Python Found 4 unhealthy pod(s): ['broken-nginx', 'oom-test', 'crashy', 'unhealthy-probe'] Analyzing pod: crashy... k8s_ai_agent.py execution Plain Text ROOT CAUSE ANALYSIS: Container is exiting immediately with code 1. The application fails to start due to a missing dependency or configuration error. DIAGNOSTIC DETAILS: - Exit Code: 1 (general application error) - Container restart count: 6 - Last termination reason: Error - No application logs available (exits too quickly) SUGGESTED REMEDIATION: 1. Review container entrypoint and command 2. Check for missing environment variables 3. Verify required config files are mounted 4. Add verbose logging to startup process IMMEDIATE ACTION: Restart the pod to clear transient state issues. The agent then prompts: Plain Text Detected CrashLoopBackOff. Suggest restarting the pod. Do you want to apply the above remediation? (yes/no): yes Upon approval, it executes: Shell kubectl delete pod crashy-77747bbb47-mr75j -n ai-apps Result: Plain Text Deployment crashy is now healthy. OOMKilled Remediation Example For memory issues: Plain Text Analyzing pod: oom-test... ROOT CAUSE ANALYSIS: Container terminated due to Out-of-Memory (OOM) condition. Memory consumption exceeded the defined limit of 128Mi. SUGGESTED REMEDIATION: Increase memory limit to accommodate actual usage patterns. Based on termination history and typical application requirements, recommend 400Mi limit. PATCH TO APPLY: spec: template: spec: containers: - name: oom-test resources: limits: memory: "400Mi" requests: memory: "200Mi" Detected OOMKilled. Suggest increasing memory limits. Do you want to apply the above remediation? (yes/no): yes The agent patches the deployment: Shell kubectl patch deployment oom-test -n ai-apps --type='json' \ -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources/limits/memory", "value":"400Mi"}]' ImagePullBackOff Handling For image issues, the agent identifies the problem but skips automatic remediation: Plain Text Analyzing pod: broken-nginx... ROOT CAUSE ANALYSIS: Failed to pull container image "nginx:nonexistent-tag". The specified image tag does not exist in the registry. DIAGNOSTIC DETAILS: - Image: nginx:nonexistent-tag - Error: manifest for nginx:nonexistent-tag not found - Registry: docker.io SUGGESTED REMEDIATION: 1. Verify image tag exists: docker pull nginx:nonexistent-tag 2. Check image name spelling and tag format 3. Ensure registry credentials are configured if using private registry 4. Update deployment with correct image reference ImagePullBackOff detected — likely an image issue. Skipping remediation. This requires manual intervention because the correct image name needs human judgment. How GPT-4 Analysis Works Context Building The agent collects context before sending to GPT-4: Python def collect_pod_context(namespace, pod_name): context = { 'pod_description': run_kubectl(['describe', 'pod', pod_name, '-n', namespace]), 'pod_logs': run_kubectl(['logs', pod_name, '-n', namespace, '--tail=100']), 'previous_logs': run_kubectl(['logs', pod_name, '-n', namespace, '--previous', '--tail=50']), 'pod_events': run_kubectl(['get', 'events', '-n', namespace, '--field-selector', f'involvedObject.name={pod_name}']) } return context Prompt Construction The system prompt guides GPT-4 to provide structured responses: Python system_prompt = """ You are a Kubernetes expert analyzing pod failures. Provide: 1. ROOT CAUSE ANALYSIS: Clear identification of the primary issue 2. DIAGNOSTIC DETAILS: Supporting evidence from events and logs 3. SUGGESTED REMEDIATION: Specific fixes with commands or YAML patches 4. IMMEDIATE ACTION: What to do right now Focus on actionable advice. For resource issues, suggest specific limits. For configuration problems, identify missing or incorrect settings. """ user_prompt = f""" Analyze this Kubernetes pod failure: POD NAME: {pod_name} NAMESPACE: {namespace} STATUS: {pod_status} DESCRIPTION: {pod_description} LOGS: {logs} EVENTS: {events} Provide detailed diagnosis and remediation steps. """ GPT-4 Response Parsing The agent extracts structured information from GPT-4's response: Python def parse_diagnosis(response): diagnosis = { 'root_cause': extract_section(response, 'ROOT CAUSE'), 'details': extract_section(response, 'DIAGNOSTIC DETAILS'), 'remediation': extract_section(response, 'SUGGESTED REMEDIATION'), 'immediate_action': extract_section(response, 'IMMEDIATE ACTION') } return diagnosis The tool implements different remediation approaches based on failure type: IssueDiagnosisAutomated ActionRationaleImagePullBackOffImage issueNone (manual)Requires human judgment on correct imageCrashLoopBackOffContainer crashPod restartClears transient state issuesOOMKilledMemory overusePatch memory limitsPrevents future OOM killsProbe failureMisconfigurationNone (manual)Needs application-level fixes Restart Remediation For CrashLoopBackOff: Python def restart_pod(namespace, pod_name): """Delete pod to trigger recreation by deployment""" run_kubectl(['delete', 'pod', pod_name, '-n', namespace]) # Wait for new pod to be ready wait_for_pod_ready(namespace, deployment_name) Memory Patch Remediation For OOMKilled: Python def patch_memory_limit(namespace, deployment_name, new_limit='400Mi'): """Patch deployment to increase memory limit""" patch = { 'spec': { 'template': { 'spec': { 'containers': [{ 'name': get_container_name(namespace, deployment_name), 'resources': { 'limits': {'memory': new_limit}, 'requests': {'memory': str(int(new_limit[:-2]) // 2) + 'Mi'} } }] } } } } run_kubectl(['patch', 'deployment', deployment_name, '-n', namespace, '--type', 'strategic', '-p', json.dumps(patch)]) Important note: The current implementation uses a fixed memory value (400Mi) as a remediation. This is a limitation — the actual memory size should be determined from historical usage patterns captured through metrics systems like Prometheus. Without access to actual resource consumption data, the tool cannot make informed decisions about appropriate memory limits. This is why Prometheus integration (discussed in the Future Work section) is necessary for production use. Human Approval Gate All remediation actions require explicit approval: Python def apply_remediation(diagnosis, namespace, resource): print(f"\n{diagnosis['immediate_action']}") print(f"\nSUGGESTED FIX:\n{diagnosis['remediation']}") response = input("\nDo you want to apply the above remediation? (yes/no): ") if response.lower() == 'yes': execute_remediation(namespace, resource, diagnosis) else: print("Skipping remediation. Manual intervention required.") Real-World Example: Complete Workflow Here's a complete diagnostic session for an OOMKilled pod: Python $ python3 k8s_ai_agent.py Enter the namespace to scan: production Found 1 unhealthy pod(s): ['api-server-7d5f8b-xk2p9'] Analyzing pod: api-server-7d5f8b-xk2p9... === GPT-4 ANALYSIS === ROOT CAUSE ANALYSIS: The api-server container was terminated by the OOM killer. Memory usage exceeded the configured limit of 256Mi. Analysis of previous terminations shows this is a recurring pattern, indicating the application's memory footprint has grown beyond the initial resource allocation. DIAGNOSTIC DETAILS: - Container terminated with reason: OOMKilled - Memory limit: 256Mi - Restart count: 8 (all OOM-related) - Last termination: 2 minutes ago - Pattern: Crashes occur approximately 15-20 minutes after startup - Application logs show increasing heap usage before termination MEMORY ANALYSIS: Based on the termination pattern and logs: - Initial memory usage: ~80Mi - Pre-crash memory usage: ~250Mi (approaching limit) - Growth pattern suggests memory leak or legitimate increased load - Recommend 512Mi limit to provide headroom SUGGESTED REMEDIATION: 1. Immediate: Increase memory limit to 512Mi 2. Set request to 384Mi (75% of limit) 3. Monitor actual usage with: kubectl top pod api-server-7d5f8b-xk2p9 -n production 4. Investigate memory leak if usage continues growing 5. Consider implementing horizontal pod autoscaling YAML PATCH: spec: template: spec: containers: - name: api-server resources: limits: memory: "512Mi" requests: memory: "384Mi" IMMEDIATE ACTION: Patch deployment with increased memory limits ====================== Detected OOMKilled. Suggest increasing memory limits. REMEDIATION PLAN: - Patch deployment 'api-server' memory limit to 512Mi - Set memory request to 384Mi - Trigger rolling restart Do you want to apply the above remediation? (yes/no): yes Applying remediation... deployment.apps/api-server patched Waiting for new pod to be ready... Pod api-server-7d5f8b-n8x4m is now Running Deployment api-server is now healthy. NEXT STEPS: 1. Monitor pod memory usage: kubectl top pod -n production 2. Check application metrics for memory leak indicators 3. Review application logs for memory-related warnings 4. Consider adding memory usage alerts What Works Well Rapid Triage The tool scans an entire namespace and identifies all unhealthy pods in seconds, providing immediate visibility into cluster health. Context Analysis GPT-4 analyzes pod descriptions, logs, and events together, correlating information that might require multiple mental steps for a human operator. Knowledge Application The model applies Kubernetes best practices and common troubleshooting patterns without requiring the operator to remember specific commands or solutions. Executable Output Rather than just identifying problems, the tool provides specific kubectl commands, YAML patches, and remediation scripts ready to execute. Safe Automation The human approval gate ensures operators review recommendations before applying changes, preventing automated mistakes in production environments. Current Limitations Single LLM Provider The POC only supports OpenAI GPT-4. Adding support for Anthropic Claude, local models via Ollama, or Azure OpenAI would improve flexibility and reduce vendor lock-in. Simple Remediation Logic Current automated fixes are limited: Pod restarts for CrashLoopBackOffMemory limit patches for OOMKilledNo automated fixes for ImagePullBackOff or probe failures More work would require: Image name validation and correctionProbe configuration analysis and fixesNetwork policy adjustmentsRBAC issue resolution Single-Container Assumption The memory patching logic assumes deployments have a single container. Multi-container pods require more analysis to determine which container needs resource adjustments. No Historical Context The agent analyzes each pod independently without considering: Previous diagnostic sessionsRemediation success/failure patternsCluster-wide trendsRelated failures in other namespaces Limited Observability Integration The tool relies solely on kubectl output. Integration with monitoring systems would provide: Historical resource usage trendsPerformance metrics before failuresApplication-specific telemetryDistributed tracing context CLI-Only Interface The current implementation is command-line interactive. Production use would benefit from: Web dashboard for visualizationAPI endpoints for integrationSlack/Teams notificationsIncident management system integration Cost Considerations Each diagnostic session calls the OpenAI API. For large clusters with many unhealthy pods, costs can accumulate. Implementing caching, local models, or rate limiting would help manage expenses. Security Concerns Sending pod logs to external APIs (OpenAI) raises data security issues: Logs may contain sensitive informationAPI keys, tokens, or credentials might leakCompliance requirements may prohibit external data transmission Production deployments need: Log sanitization to remove sensitive dataLocal LLM options for sensitive environmentsAudit trails of what data was sent externally Future Work Multi-Provider LLM Support Add support for alternative models: Python class LLMProvider: def __init__(self, provider='openai', model='gpt-4'): self.provider = provider self.model = model def analyze(self, context): if self.provider == 'openai': return self._openai_analyze(context) elif self.provider == 'anthropic': return self._claude_analyze(context) elif self.provider == 'ollama': return self._ollama_analyze(context) Prometheus Integration Incorporate time-series metrics: Python def enhance_context_with_metrics(namespace, pod_name): metrics = { 'cpu_usage': query_prometheus( f'rate(container_cpu_usage_seconds_total{{pod="{pod_name}"}[5m])' ), 'memory_usage': query_prometheus( f'container_memory_working_set_bytes{{pod="{pod_name}"}' ), 'restart_history': query_prometheus( f'kube_pod_container_status_restarts_total{{pod="{pod_name}"}' ) } return metrics This integration would solve the current limitation where OOMKilled remediation uses fixed memory values (400Mi). With Prometheus data, the tool could analyze actual memory usage patterns over time and recommend appropriate limits based on real consumption trends rather than arbitrary values. Feedback Loop Track remediation success to improve future diagnostics: Python class RemediationTracker: def record_outcome(self, pod_name, diagnosis, action, success): """Track which fixes worked""" outcome = { 'pod': pod_name, 'issue_type': diagnosis['type'], 'action_taken': action, 'successful': success, 'timestamp': datetime.now() } self.store_outcome(outcome) def get_success_rate(self, issue_type): """Calculate success rate for specific issue types""" outcomes = self.query_outcomes(issue_type) return sum(o['successful'] for o in outcomes) / len(outcomes) Expanded Remediation Expand automated fixes: Python class AdvancedRemediation: def fix_image_pull_error(self, namespace, pod_name, diagnosis): """Attempt to fix common image pull issues""" # Check if image exists with 'latest' tag # Verify imagePullSecrets are configured # Test registry connectivity # Suggest alternative image sources pass def fix_probe_failure(self, namespace, deployment, diagnosis): """Adjust probe configuration based on actual app behavior""" # Analyze startup time from logs # Recommend appropriate initialDelaySeconds # Suggest probe endpoint alternatives pass Web Dashboard Build a visualization layer: Python // React component for real-time diagnostics function DiagnosticsDashboard() { const [pods, setPods] = useState([]); const [analyses, setAnalyses] = useState({}); useEffect(() => { // Poll for unhealthy pods fetchUnhealthyPods().then(setPods); }, []); return ( <div> <PodList pods={pods} onAnalyze={runDiagnostics} /> <AnalysisPanel analyses={analyses} /> <RemediationQueue onApprove={applyFix} /> </div> ); } Incident Management Integration Connect to existing workflows: Python def create_incident_with_diagnosis(pod_name, diagnosis): """Create PagerDuty incident with analysis""" incident = { 'title': f'Pod Failure: {pod_name}', 'description': diagnosis['root_cause'], 'urgency': determine_urgency(diagnosis), 'body': { 'type': 'incident_body', 'details': format_diagnosis_for_incident(diagnosis) } } pagerduty_client.create_incident(incident) Getting Started Quick Start Shell # Clone and setup git clone https://github.com/opscart/k8s-ai-diagnostics.git cd k8s-ai-diagnostics python3 -m venv venv source venv/bin/activate pip install -r requirements.txt # Set OpenAI API key export OPENAI_API_KEY="your-key" # Deploy test scenarios kubectl create namespace ai-apps sh k8s-manifests/deploy.sh # Run diagnostics python3 k8s_ai_agent.py # Enter namespace: ai-apps Production Considerations Before using in production: Test in non-production environments – Verify remediation logic doesn't cause unintended consequencesImplement log sanitization – Remove sensitive data before sending to OpenAISet up monitoring – Track diagnostic success rates and API costsConfigure rate limiting – Prevent API quota exhaustionDocument approval workflows – Define who can approve which types of remediationEstablish rollback procedures – Know how to revert automated changes Conclusion The k8s-ai-diagnostics project demonstrates that LLMs can automate routine Kubernetes troubleshooting tasks. By combining kubectl's data collection capabilities with GPT-4's analytical reasoning, the tool provides diagnostic insights that previously required experienced SRE intervention. The POC shows particular strength in handling common failure patterns like CrashLoopBackOff and OOMKilled scenarios, where automated remediation can reduce MTTR. The human approval gate maintains safety while allowing operators to move quickly when confident in the recommendations. However, the current implementation has clear limitations. Production readiness requires addressing security concerns around data transmission, expanding remediation capabilities beyond simple cases, and integrating with existing observability and incident management infrastructure. The OOMKilled remediation, for example, currently uses fixed memory values rather than analyzing actual usage patterns — a gap that Prometheus integration would fill. For teams experiencing high volumes of routine pod failures, this approach offers a way to reduce operational toil. The tool handles repetitive diagnostic work, letting engineers focus on complex issues that require human judgment and problem-solving. As observability integration improves and remediation logic matures, LLM-augmented troubleshooting will become more viable for production environments. Additional Resources GitHub repository: opscart/k8s-ai-diagnosticsKubernetes troubleshooting: kubernetes.io/docs/tasks/debugOpenAI API documentation: platform.openai.com/docskubectl reference: kubernetes.io/docs/reference/kubectl

By Shamsher Khan
Theia Deep Dive, Part 1: From Zero to Your Own IDE
Theia Deep Dive, Part 1: From Zero to Your Own IDE

Intro: Why Build Your Own IDE? There are countless reasons to create your own IDE. Perhaps you are creating a sandbox similar to CodeSandbox, but adapted to your own framework. Or an Arduino-style environment where a strictly controlled user interface is required for flashing firmware and interacting with hardware. Maybe your SaaS product needs a built-in cloud editor. Maybe you're creating an educational platform where users should only see part of the code. Or you're releasing your own language or framework, and want the developer tools not to look like an add-on plugin. Or maybe you've decided to create your own Cursor with an AI assistant. Whatever your goal, creating your own IDE is more than possible. In this guide, I'll show you how to do it with Eclipse Theia: a fully modular, open-source platform designed to adapt to your needs. Our Goals I will proceed from the idea that we want to create an online IDE, similar to CodeSandbox. It's great to have a full-fledged IDE with support for dozens of languages, a full-fledged file system, a terminal, and no need to write everything from scratch. At the same time, we don't need all the functionality that any IDE usually offers; for example, functionality such as Debug may be unnecessary. So here's what I'll do: Remove any distracting elements, buttons, and panels, leaving only the essentialsRemove unnecessary menus, buttons, and actionsAdd our own menus and actionsConfigure the default layoutConnect the necessary themes and widgetsMake unique looks and feel with Islands UI So, let's dive in! Meet Theia: The Framework Behind the Magic To bring our idea to life, we will use Eclipse Theia. This is an open-source modular framework for creating IDEs. It looks and works like VS Code and partially uses VS Code components (like Monaco Editor). But it is not a fork of VS Code. It supports VS Code extensions, but is not tied to VS Code either architecturally or visually. Theia was conceived as a universal framework for custom IDEs. It gives you a foundation and a set of separate modules: from a code editor to a terminal, from toolbars to tabs. Everything can be disabled, replaced, or rewritten. And you can make your own modules with their internal widget system, which allows you to write your own components in React (or whatever framework you want, actually). You can build a specialized IDE, such as Arduino IDE or SAP App Studio. Or a minimalistic editor with an AI agent inside, like Cursor. Or something completely non-standard, such as an editor where you can't write code, but you can build diagrams. It all depends on your requirements. It works both in a browser and as a desktop application (via Electron), but I will focus on the web version. The Electron version works similarly in the aspects that will be discussed in this article. Why Not Use VS Code Web? Although VS Code is open source, it is not a modular framework. It is a monolithic product with a plugin system, rather than a set of components and modules that can be assembled into the specific tool you need. You can add extensions, but you cannot remove basic parts such as panels, commands, tabs, and Shell behavior without hacks and serious code rewriting, which can make it difficult to update the VS Code core in the future. And even though VS Code is an open-source product, Microsoft is not interested in you using it to create competitors. So forget about documentation or official support. Also, according to my tests, VS Code consumes about 1.5 times more memory, which can be critical if you run it inside virtual machine instances somewhere in the cloud. You can even run a "browser-only" version of Theia with some kind of virtual file system; in that case, you do not need backend resources at all. Installing Theia and Preparing to Make Changes The first step is to deploy the IDE code itself. We have two options: Clone the full repository. The repository contains a complete build with all plugins. This is the same version you can download and install on your PC from the main page: https://theia-ide.org/, but it's heavy and contains a lot of unnecessary components. My goal is to create a lightweight Cloud IDE build, so this method won't work for me, as it would require more effort to remove unnecessary parts.Use the extension generator. The idea of this generator is to provide a CLI for creating and debugging custom Theia plugins, but when this CLI creates a plugin, it deploys a minimal Theia build that contains nothing except a file tree and code editor. Let's start with this one. To install, run: TypeScript npm install -g yo generator-theia-extension This will install the necessary utilities for generation. To create a sample Theia project (optionally with custom Theia extensions), including a browser and electron app, run: TypeScript mkdir ~/theia && cd ~/theia yo theia-extension Select “Hello World” and specify the name of your extension, for example custom-ui. This will create a minimal possible IDE configuration for browser and electron, an example plugin, and will immediately install the dependencies. Prepare for Customization Before we start customizing our IDE, we'll make a few changes to the build and launch process. First, we'll replace the outdated lerna with turborepo and remove scripts related to the electron version since we won't need it: TypeScript npm remove lerna npm install turbo -D Turborepo is necessary in this project because it contains multiple packages and custom plugins that we'll be writing, each of which needs to be built separately. With turborepo, we can build all our dependencies with a single command. I'll also delete the electron-app folder since I don't need it and remove all electron version-related scripts so they don't get in the way, and I'll change the port to :4000 to avoid conflicts with other applications. Open /package.json and replace lerna with turborepo and remove unnecessary scripts: ~/theia/package.json TypeScript { "private": true, "engines": { "node": ">=20", "npm": ">=10" }, "scripts": { "build": "turbo run build", "start": "turbo start", "watch": "turbo run watch", "postinstall": "theia check:theia-version", "clean": "turbo run clean && rimraf node_modules", "download:plugins": "theia download:plugins --rate-limit=15 --parallel=false", }, "workspaces": [ "custom-ui", "browser-app" ], "devDependencies": { "turbo": "^2.5.4", "typescript": "^5.8.3", "rimraf": "^6.0.1" }, "packageManager": "[email protected]", "theiaPluginsDir": "plugins", "theiaPlugins": {} } ~/theia/browser-app/package.json TypeScript { "private": true, "name": "browser-app", "version": "0.0.0", "dependencies": { "@theia/core": "1.62.2", "@theia/editor": "1.62.2", "@theia/filesystem": "1.62.2", "@theia/markers": "1.62.2", "@theia/messages": "1.62.2", "@theia/monaco": "1.62.2", "@theia/navigator": "1.62.2", "@theia/preferences": "1.62.2", "@theia/process": "1.62.2", "@theia/terminal": "1.62.2", "@theia/workspace": "1.62.2", "custom-ui": "0.0.0" }, "devDependencies": { "@theia/cli": "1.62.2" }, "scripts": { "build": "npm run rebuild && theia build --mode production", "rebuild": "theia rebuild:browser --cacheRoot ..", "start": "theia start -p 4000 --plugins=local-dir:../plugins", "watch": "npm run rebuild && theia build --watch --mode development", "clean": "theia clean && rimraf node_modules" }, "theia": { "target": "browser" }, "packageManager": "[email protected]" } Now, create a file turbo.json. ~/theia/turbo.json TypeScript { "$schema": "<https://turbo.build/schema.json>", "tasks": { "clean": { "outputs": [], "dependsOn": ["^clean"] }, "build": { "dependsOn": ["^build"], "outputs": ["lib/**"] }, "start": { "dependsOn": ["^build"], "outputs": [] }, "watch": { "cache": false, "dependsOn": [], "outputs": ["lib/**"] } } } I also recommend using vite as the bundler for our custom-ui plugin, as this will solve several problems down the line and allow us to easily add necessary plugins to our plugin, such as Tailwind or preprocessor support if needed. First, let's specify paths in tsconfig and update target and lib to more modern versions: ~/theia/custom-ui/tsconfig.json TypeScript { "compilerOptions": { "skipLibCheck": true, "declaration": true, "declarationMap": true, "noImplicitAny": true, "noEmitOnError": false, "noImplicitThis": true, "noUnusedLocals": true, "strictNullChecks": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "resolveJsonModule": true, "module": "commonjs", "moduleResolution": "node", "target": "ES2022", "jsx": "react", "lib": ["ES2022", "dom"], "sourceMap": true, "rootDir": ".", "outDir": "lib", "paths": { "@/*": ["./src/*"] } }, "include": ["src"], "exclude": ["node_modules", "lib"] } Create vite.config.mjs: ~/theia/custom-ui/vite.config.mjs TypeScript import react from '@vitejs/plugin-react'; import { resolve } from 'path'; import { defineConfig } from 'vite'; import { libInjectCss } from 'vite-plugin-lib-inject-css'; export default defineConfig({ clearScreen: false, plugins: [ react(), libInjectCss(), ], build: { lib: { entry: [ resolve(__dirname, 'src/frontend/index.ts'), ], formats: ['cjs'], fileName: (format, entryName) => `${ entryName }.js`, }, outDir: 'lib', emptyOutDir: true, cssCodeSplit: true, sourcemap: true, target: 'ES2022', rollupOptions: { external: (id) => { // Aliases is not external if (id.startsWith('@/')) { return false; } return !id.startsWith('.') && !id.startsWith('/') && !id.includes('\\0'); }, output: { preserveModules: true, preserveModulesRoot: 'src', entryFileNames: '[name].js', chunkFileNames: '[name].js', format: 'cjs', exports: 'named', }, }, }, resolve: { alias: [{ find: /^@\\/(.*)/, replacement: `${ resolve(__dirname, 'src') }/$1`, }], // preserveSymlinks: true, }, }); Let's specify the scripts and entry points in package.json. ~/theia/custom-ui/package.json TypeScript { ... "scripts": { "clean": "rm -rf lib node_modules", "build": "vite build && npm run dts", "watch": "vite build --watch --clearScreen false", "dev": "npm run watch", "dts": "tsc --emitDeclarationOnly --declaration --outDir lib" }, "theiaExtensions": [{ "frontend": "lib/frontend/index.js" }] } Let's create the main file for our plugin, for now, without any logic. ~/theia/custom-ui/src/frontend/index.ts TypeScript import { ContainerModule } from '@theia/core/shared/inversify'; export default new ContainerModule((bind, unbind, isBound, rebind) => { // TODO Implement any logic here }); Install Vite: TypeScript cd ~/**theia/custom-ui npm i -D vite @vitejs/plugin-react vite-plugin-lib-inject-css Check that everything runs smoothly: TypeScript cd ~/theia npm run build npm run start Open http://127.0.0.1:4000 and check your working IDE: Adding Functionality Currently, our IDE has literally nothing except a file tree and an editor without syntax highlighting support, so let's add the missing plugins. VS Code Plugins VS Code plugins (pre-installed by you or installed by users from the marketplace). These are all the plugins from the VS Code application store, as well as some internal plugins, such as syntax highlighting support for different languages. To install such a plugin, you can use the Extensions section or declare it in package.json → theiaPlugins Let's start by installing basic VS Code plugins for syntax highlighting, as well as the material icons theme: ./package.json TypeScript "theiaPlugins": { "eclipse-theia.builtin-extension-pack": "<https://open-vsx.org/api/eclipse-theia/builtin-extension-pack/1.95.3/file/eclipse-theia.builtin-extension-pack-1.95.3.vsix>", "zhuangtongfa.material-theme": "<https://open-vsx.org/api/zhuangtongfa/material-theme/3.19.0/file/zhuangtongfa.material-theme-3.19.0.vsix>", "PKief.material-icon-theme": "<https://open-vsx.org/api/PKief/material-icon-theme/5.23.0/file/PKief.material-icon-theme-5.23.0.vsix>" } eclipse-theia.builtin-extension-pack includes all plugins contained in the standard VS Code build, but you can install only the ones you need: TypeScript "theiaPlugins": { "vscode.javascript": "<https://open-vsx.org/api/vscode/javascript/1.95.3/file/vscode.javascript-1.95.3.vsix>", "vscode.typescript": "<https://open-vsx.org/api/vscode/typescript/1.95.3/file/vscode.typescript-1.95.3.vsix>", "vscode.typescript-language-features": "<https://open-vsx.org/api/vscode/typescript-language-features/1.95.3/file/vscode.typescript-language-features-1.95.3.vsix>", "vscode.json": "<https://open-vsx.org/api/vscode/json/1.95.3/file/vscode.json-1.95.3.vsix>", "vscode.css": "<https://open-vsx.org/api/vscode/css/1.95.3/file/vscode.css-1.95.3.vsix>", "vscode.html": "<https://open-vsx.org/api/vscode/html/1.95.3/file/vscode.html-1.95.3.vsix>", "vscode.markdown": "<https://open-vsx.org/api/vscode/markdown/1.95.3/file/vscode.markdown-1.95.3.vsix>", "zhuangtongfa.material-theme": "<https://open-vsx.org/api/zhuangtongfa/material-theme/3.19.0/file/zhuangtongfa.material-theme-3.19.0.vsix>", "PKief.material-icon-theme": "<https://open-vsx.org/api/PKief/material-icon-theme/5.23.0/file/PKief.material-icon-theme-5.23.0.vsix>" } zhuangtongfa.material-theme — one dark theme, one of the most popular themes for VS CodePKief.material-icon-theme — material style icons in the file tree and tabs Then execute: TypeScript npm run download:plugins This will download all these plugins to the /plugins folder. Core Plugins These are plugins that extend Theia's functionality. These plugins add core IDE functionality and can completely change behavior. To install such a plugin, simply run npm install for the desired package. The list of packages can be found in the Theia repository. I will install the most essential ones for me, which are project search and VS Code plugin support: TypeScript cd **~/**theia/browser-app npm i @theia/plugin @theia/plugin-ext @theia/plugin-ext-vscode @theia/search-in-workspace Please note that some plugins may depend on others, so when you install @theia/plugin-ext-vscode, almost all the main modules will be included in your build, even if you don't need them. Then run npm run build && npm run start to check that everything is working. Rewiring Theia: UI and Behavior Customization To understand how to customize Theia, we need to understand how its code works internally. Any customizations are done through system plugins, whose code can replace any system object or class. This is possible thanks to InversifyJS, which Theia uses under the hood. It is a simple but incredibly powerful Dependency Injection container that allows you to easily replace some dependencies with others. This way, as a developer of the custom-ui plugin, you can easily override the logic of system modules, such as @theia/terminal on the front end or @theia/file-service on the back end. Now we are ready to start customizing our build. My plan includes: Specify default user settingsCreate a splash screenRemove unnecessary interface elements, functionality, actions, and menusAdd custom commands and menu itemsChange the appearance of some interface elementsChange some behavior logic (for example, prohibit DnD elements on panels) Default IDE Settings Theia uses the same configuration format as VS Code and is configured via the settings.json file. To set the default settings, we first need to collect them. To do this, open your IDE, configure it in any convenient way (via the interface or directly via the JSON config), then press cmd + shift + p to open the Command Palette and enter “Open Settings JSON.” Copy the contents of this file and paste them into browser-app/package.json in the theia section: ./browser-app/package.json TypeScript "theia": { "target": "browser", "frontend": { "config": { "applicationName": "Flexbe IDE", "reloadOnReconnect": false, "preferences": { "editor.indentSize": "tabSize", "editor.tabSize": 4, "workbench.tree.indent": 13, "workbench.colorTheme": "One Dark Pro Night Flat", "workbench.iconTheme": "material-icon-theme" } } }, } Creating a Splash Screen This screen will be displayed during the loading process of Theia instead of the standard loader. To create it, specify the template in the settings of Theia → generator. ./browser-app/package.json TypeScript "theia": { ... "generator": { "config": { "preloadTemplate": "./resources/preload.html" } } } After that, we will correct the webpack configuration so that static files are copied to the frontend assets folder during compilation, and so that we can see the compilation progress: ./browser-app/webpack.config.js TypeScript const CopyWebpackPlugin = require('copy-webpack-plugin'); const path = require('path'); const webpack = require('webpack'); const configs = require('./gen-webpack.config.js'); const nodeConfig = require('./gen-webpack.node.config.js'); // Add copy plugin and progress plugin to the first config configs[0].plugins.push( new CopyWebpackPlugin({ patterns: [ { from: path.resolve(__dirname, './resources'), to: path.resolve(__dirname, './lib/frontend/resources'), }, ], }), new webpack.ProgressPlugin((percentage, message, ...args) => { const cleanMessage = `${ (percentage * 100).toFixed(1) }% ${ message } ${ args.join(' ') }`.trim(); console.warn(cleanMessage); }) ); module.exports = [ ...configs, nodeConfig.config, ]; Create a resources folder with a preload.html file: ./browser-app/resources/preload.html TypeScript <style> html, body { background-color: #23272e; } .theia-preload { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-image: none; } .theia-preload::after { /* remove default loading animation */ content: none; } .spinner-container { display: flex; flex-direction: center; align-self: center; justify-content: center; height: 100vh; width: 100vw; background-color: #23272e; } .custom-spinner { display: flex; flex-direction: center; align-self: center; justify-content: center; animation: fadeInOut 1.65s ease-in-out infinite; } @keyframes fadeInOut { 0% { opacity: 0.4; } 50% { opacity: 1; } 100% { opacity: 0.4; } } </style> <div class="spinner-container"> <div class="custom-spinner"> <svg width="150" height="30" viewBox="0 0 368 74" fill="none" xmlns="<http://www.w3.org/2000/svg>"> <path d="M27.0691 12.8665C22.672 12.8665 19.1076 16.4384 19.1076 20.8445V28.8226H31.846V38.3964H19.1076H9.55379H0V28.8226H9.55379V20.8445C9.55379 11.1509 17.3957 3.29272 27.0691 3.29272H31.846V12.8665H27.0691ZM19.1076 38.3964V73.5H9.55379V47.9699L19.1076 38.3964ZM171.968 3.29272H162.414V73.5H171.968V65.8373C176.014 70.5661 181.653 73.5 187.891 73.5C200.203 73.5 210.183 62.0699 210.183 47.9699C210.183 33.8703 200.203 22.4402 187.891 22.4402C181.653 22.4402 176.014 25.3742 171.968 30.1029V3.29272ZM186.299 63.9265C177.933 63.9265 171.968 56.3535 171.968 47.9699C171.968 39.5868 177.933 32.0139 186.299 32.0139C194.665 32.0139 200.629 39.5868 200.629 47.9699C200.629 56.3535 194.665 63.9265 186.299 63.9265ZM155.874 22.4402H144.728L131.99 40.2003L126.416 47.9699L108.105 73.5H119.251L131.99 55.7399L144.728 73.5H155.874L137.563 47.9699L155.874 22.4402ZM119.251 22.4402L129.232 36.3559L123.63 44.0851L108.105 22.4402H119.251ZM38.2151 3.29272V62.3308C38.2151 68.4993 43.2052 73.5 49.3611 73.5H54.1383V63.9265H49.3611C48.4817 63.9265 47.7689 63.2119 47.7689 62.3308V3.29272H38.2151ZM69.862 26.3473C73.2072 23.9098 77.494 22.4402 82.7996 22.4402C88.1048 22.4402 92.3916 23.9099 95.7364 26.3477C99.0298 28.7483 101.13 31.8807 102.468 34.8075C103.802 37.7257 104.444 40.5806 104.761 42.6619C104.921 43.7142 105.002 44.6007 105.044 45.2412C105.065 45.5622 105.076 45.8235 105.082 46.0154C105.085 46.1114 105.086 46.1906 105.087 46.251L105.088 46.3278L105.088 46.3559V46.3672V46.3723C105.088 46.3723 105.088 46.3766 100.311 46.375C100.311 51.1616 100.311 51.1616 100.311 51.1616H100.305H100.299H100.294H100.289H100.283H100.277H100.27H100.262H100.254H100.244H100.234H100.224H100.213H100.201H100.189H100.176H100.162H100.148H100.133H100.117H100.101H100.084H100.067H100.049H100.03H100.011H99.991H99.9702H99.9491H99.9272H99.9049H99.8819H99.8581H99.8339H99.8093H99.7836H99.7579H99.7314H99.7041H99.6761H99.648H99.6188H99.5897H99.5593H99.5286H99.4975H99.4656H99.4334H99.4003H99.3669H99.3331H99.2982H99.2632H99.2275H99.1914H99.1546H99.1173H99.0793H99.0409H99.0021H98.9626H98.9226H98.8819H98.8408H98.7993H98.7571H98.7145H98.6715H98.6277H98.5835H98.539H98.4937H98.448H98.4019H98.355H98.3082H98.2602H98.2122H98.1634H98.1143H98.0647H98.0148H97.9641H97.913H97.8616H97.8097H97.7571H97.7041H97.6507H97.597H97.5428H97.4879H97.4326H97.3769H97.3209H97.2644H97.2072H97.15H97.092H97.0336H96.9749H96.9157H96.8562H96.7959H96.7356H96.6746H96.6135H96.5517H96.4895H96.4269H96.3643H96.3009H96.2372H96.1727H96.1081H96.0432H95.978H95.9123H95.8462H95.7794H95.7126H95.6454H95.5778H95.5098H95.4415H95.3724H95.3032H95.2337H95.1638H95.0939H95.0233H94.9522H94.8808H94.8094H94.7372H94.665H94.592H94.5191H94.4457H94.372H94.2979H94.2238H94.1489H94.074H93.9987H93.9231H93.847H93.7706H93.6938H93.617H93.5398H93.4622H93.3843H93.3063H93.2276H93.1489H93.0702H92.9907H92.9112H92.8313H92.7511H92.6704H92.5898H92.5087H92.4273H92.3459H92.2641H92.1819H92.0994H92.0168H91.9339H91.8509H91.7672H91.6839H91.5998H91.5157H91.4312H91.3467H91.2618H91.1766H91.0913H91.0057H90.92H90.834H90.7476H90.6612H90.5744H90.4876H90.4005H90.3129H90.2254H90.1378H90.0499H89.9615H89.8732H89.7845H89.6958H89.6071H89.518H89.4285H89.339H89.2492H89.1593H89.0695H88.9792H88.889H88.7983H88.7073H88.6167H88.5257H88.4343H88.3429H88.2515H88.1597H88.0679H87.9762H87.884H87.7914H87.6993H87.6067H87.5142H87.4212H87.3283H87.2354H87.1421H87.0487H86.9554H86.8617H86.768H86.6743H86.5806H86.4865H86.3925H86.2984H86.2039H86.1094H86.015H85.9205H85.8256H85.7312H85.6363H85.5415H85.4462H85.3514H85.2561H85.1609H85.0657H84.9704H84.8748H84.7792H84.684H84.5883H84.4927H84.3967H84.3011H84.2055H84.1095H84.0135H83.9174H83.8218H83.7258H83.6294H83.5334H83.4374H83.3414H83.245H83.149H83.053H82.9566H82.8606H82.7642H82.6682H82.5718H82.4755H82.3795H82.2831H82.1871H82.0907H81.9947H81.8983H81.8023H81.7059H81.6099H81.5139H81.4175H81.3215H81.2255H81.1295H81.0335H80.9378H80.8418H80.7458H80.6502H80.5542H80.4586H80.363H80.2673H80.1717H80.0765H79.9809H79.8856H79.7904H79.6952H79.5999H79.5051H79.4098H79.315H79.2201H79.1257H79.0308H78.9363H78.8419H78.7474H78.6533H78.5588H78.4648H78.3711H78.277H78.1833H78.0896H77.9963H77.9026H77.8092H77.7163H77.6234H77.5304H77.4375H77.345H77.2524H77.1599H77.0677H76.9755H76.8838H76.792H76.7002H76.6088H76.5174H76.426H76.335H76.244H76.1534H76.0627H75.9725H75.8822H75.7924H75.7025H75.6126H75.5232H75.4341H75.345H75.2559H75.1672H75.0785H74.9902H74.9022H74.8143H74.7263H74.6388H74.5516H74.4644H74.3776H74.2909H74.2044H74.118H74.032H73.9464H73.8608H73.7755H73.6902H73.6054H73.5209H73.4364H73.3523H73.2686H73.1849H73.1015H73.0182H72.9357H72.8527H72.7705H72.6883H72.6065H72.5251H72.4437H72.3627H72.2821H72.2014H72.1215H72.0416H71.9618H71.8827H71.8036H71.7248H71.6465H71.5682H71.4906H71.413H71.3358H71.2586H71.1822H71.1058H71.0301H70.9545H70.8788H70.804H70.7295H70.655H70.5808H70.5071H70.4338H70.3608H70.2882H70.216H70.1961C71.0079 55.8698 73.8892 59.4964 77.7689 61.4404C82.7535 63.9378 90.0107 63.9198 97.5014 58.4601L103.121 66.2024C93.0986 73.5074 82.0461 74.2871 73.4963 70.0033C65.016 65.7542 59.614 56.7802 60.5072 46.1274L60.5103 46.0135C60.5161 45.8216 60.5272 45.5602 60.548 45.2392C60.5898 44.5988 60.6716 43.7126 60.8321 42.6599C61.1497 40.5786 61.7933 37.7241 63.1282 34.8062C64.4669 31.8796 66.5678 28.7476 69.862 26.3473ZM100.311 46.375L105.088 46.3766L105.086 51.1616H100.311L100.311 46.375ZM70.8097 41.5881H70.8788H70.9545H71.0301H71.1058H71.1822H71.2586H71.3358H71.413H71.4906H71.5682H71.6465H71.7248H71.8036H71.8827H71.9618H72.0416H72.1215H72.2014H72.2821H72.3627H72.4437H72.5251H72.6065H72.6883H72.7705H72.8527H72.9357H73.0182H73.1015H73.1849H73.2686H73.3523H73.4364H73.5209H73.6054H73.6902H73.7755H73.8608H73.9464H74.032H74.118H74.2044H74.2909H74.3776H74.4644H74.5516H74.6388H74.7263H74.8143H74.9022H74.9902H75.0785H75.1672H75.2559H75.345H75.4341H75.5232H75.6126H75.7025H75.7924H75.8822H75.9725H76.0627H76.1534H76.244H76.335H76.426H76.5174H76.6088H76.7002H76.792H76.8838H76.9755H77.0677H77.1599H77.2524H77.345H77.4375H77.5304H77.6234H77.7163H77.8092H77.9026H77.9963H78.0896H78.1833H78.277H78.3711H78.4648H78.5588H78.6533H78.7474H78.8419H78.9363H79.0308H79.1257H79.2201H79.315H79.4098H79.5051H79.5999H79.6952H79.7904H79.8856H79.9809H80.0765H80.1717H80.2673H80.363H80.4586H80.5542H80.6502H80.7458H80.8418H80.9378H81.0335H81.1295H81.2255H81.3215H81.4175H81.5139H81.6099H81.7059H81.8023H81.8983H81.9947H82.0907H82.1871H82.2831H82.3795H82.4755H82.5718H82.6682H82.7642H82.8606H82.9566H83.053H83.149H83.245H83.3414H83.4374H83.5334H83.6294H83.7258H83.8218H83.9174H84.0135H84.1095H84.2055H84.3011H84.3967H84.4927H84.5883H84.684H84.7792H84.8748H84.9704H85.0657H85.1609H85.2561H85.3514H85.4462H85.5415H85.6363H85.7312H85.8256H85.9205H86.015H86.1094H86.2039H86.2984H86.3925H86.4865H86.5806H86.6743H86.768H86.8617H86.9554H87.0487H87.1421H87.2354H87.3283H87.4212H87.5142H87.6067H87.6993H87.7914H87.884H87.9762H88.0679H88.1597H88.2515H88.3429H88.4343H88.5257H88.6167H88.7073H88.7983H88.889H88.9792H89.0695H89.1593H89.2492H89.339H89.4285H89.518H89.6071H89.6958H89.7845H89.8732H89.9615H90.0499H90.1378H90.2254H90.3129H90.4005H90.4876H90.5744H90.6612H90.7476H90.834H90.92H91.0057H91.0913H91.1766H91.2618H91.3467H91.4312H91.5157H91.5998H91.6839H91.7672H91.8509H91.9339H92.0168H92.0994H92.1819H92.2641H92.3459H92.4273H92.5087H92.5898H92.6704H92.7511H92.8313H92.9112H92.9907H93.0702H93.1489H93.2276H93.3063H93.3843H93.4622H93.5398H93.617H93.6938H93.7706H93.847H93.9231H93.9987H94.074H94.1489H94.2238H94.2979H94.372H94.4457H94.5191H94.592H94.665H94.7372H94.7844C94.536 40.6816 94.2096 39.7299 93.7818 38.7942C92.9315 36.9342 91.7488 35.2797 90.1167 34.0901C88.5357 32.9377 86.2534 32.0139 82.7996 32.0139C79.3453 32.0139 77.0627 32.9377 75.4805 34.0904C73.8477 35.2804 72.6638 36.9354 71.8128 38.7958C71.385 39.7311 71.0582 40.682 70.8097 41.5881ZM238.845 22.4402C233.539 22.4402 229.252 23.9098 225.907 26.3473C222.613 28.7476 220.512 31.8796 219.173 34.8062C217.838 37.7241 217.195 40.5786 216.877 42.6599C216.717 43.7126 216.635 44.5988 216.593 45.2392C216.572 45.5602 216.561 45.8216 216.555 46.0135L216.552 46.1274C215.659 56.7802 221.062 65.7542 229.541 70.0033C238.091 74.2871 249.144 73.5074 259.166 66.2024L253.546 58.4601C246.056 63.9198 238.799 63.9378 233.814 61.4404C229.934 59.4964 227.053 55.8698 226.241 51.1616H226.261H226.333H226.406H226.479H226.552H226.626H226.7H226.775H226.849H226.924H227H227.075H227.151H227.227H227.304H227.381H227.458H227.536H227.613H227.692H227.77H227.849H227.928H228.007H228.087H228.167H228.247H228.327H228.408H228.489H228.57H228.652H228.733H228.816H228.898H228.981H229.063H229.147H229.23H229.314H229.397H229.482H229.566H229.65H229.735H229.821H229.906H229.992H230.077H230.163H230.25H230.336H230.423H230.51H230.597H230.684H230.771H230.859H230.947H231.035H231.124H231.212H231.301H231.39H231.479H231.568H231.658H231.748H231.837H231.927H232.018H232.108H232.198H232.289H232.38H232.471H232.563H232.654H232.745H232.837H232.929H233.021H233.113H233.205H233.298H233.39H233.483H233.576H233.668H233.761H233.855H233.948H234.041H234.135H234.228H234.322H234.416H234.51H234.604H234.698H234.793H234.887H234.981H235.076H235.171H235.265H235.36H235.455H235.55H235.645H235.74H235.836H235.931H236.026H236.122H236.217H236.312H236.408H236.504H236.6H236.695H236.791H236.887H236.983H237.079H237.175H237.271H237.367H237.463H237.559H237.655H237.751H237.847H237.943H238.04H238.136H238.232H238.328H238.425H238.521H238.617H238.713H238.809H238.906H239.002H239.098H239.194H239.291H239.387H239.483H239.579H239.675H239.771H239.867H239.963H240.059H240.155H240.251H240.346H240.442H240.538H240.633H240.729H240.825H240.92H241.016H241.111H241.206H241.301H241.396H241.491H241.587H241.681H241.776H241.871H241.966H242.06H242.155H242.249H242.343H242.438H242.532H242.626H242.719H242.813H242.907H243.001H243.094H243.187H243.281H243.373H243.466H243.559H243.652H243.744H243.837H243.929H244.021H244.113H244.205H244.297H244.388H244.479H244.571H244.662H244.753H244.843H244.934H245.024H245.115H245.204H245.294H245.384H245.474H245.563H245.652H245.741H245.83H245.918H246.007H246.095H246.183H246.27H246.358H246.446H246.533H246.62H246.706H246.793H246.879H246.965H247.051H247.136H247.222H247.307H247.392H247.476H247.561H247.645H247.729H247.813H247.896H247.979H248.062H248.144H248.227H248.309H248.391H248.472H248.554H248.635H248.716H248.796H248.876H248.956H249.036H249.115H249.194H249.273H249.351H249.429H249.507H249.585H249.662H249.739H249.816H249.892H249.968H250.044H250.119H250.194H250.269H250.343H250.417H250.491H250.564H250.638H250.71H250.782H250.855H250.926H250.997H251.068H251.139H251.209H251.279H251.348H251.418H251.487H251.555H251.623H251.691H251.758H251.825H251.891H251.957H252.023H252.088H252.153H252.218H252.282H252.346H252.409H252.472H252.535H252.597H252.659H252.72H252.781H252.841H252.901H252.961H253.02H253.079H253.137H253.195H253.252H253.31H253.366H253.422H253.478H253.533H253.588H253.642H253.696H253.749H253.802H253.855H253.907H253.958H254.009H254.06H254.11H254.16H254.209H254.257H254.306H254.353H254.4H254.447H254.493H254.539H254.584H254.629H254.673H254.717H254.76H254.802H254.844H254.886H254.927H254.968H255.008H255.047H255.086H255.124H255.162H255.2H255.237H255.273H255.308H255.344H255.378H255.412H255.445H255.478H255.511H255.543H255.574H255.604H255.635H255.664H255.693H255.722H255.749H255.777H255.803H255.829H255.854H255.879H255.903H255.927H255.95H255.972H255.994H256.015H256.036H256.056H256.075H256.094H256.112H256.129H256.146H256.163H256.178H256.193H256.207H256.221H256.234H256.247H256.258H256.269H256.28H256.29H256.299H256.307H256.315H256.322H256.329H256.334H256.339H256.344H256.353C256.353 51.1616 256.356 51.1616 256.356 46.375L256.353 51.1616H261.131L261.133 46.3766L256.356 46.375C261.133 46.3766 261.133 46.3723 261.133 46.3723V46.3672V46.3559L261.133 46.3278L261.132 46.251C261.131 46.1906 261.13 46.1114 261.127 46.0154C261.121 45.8235 261.11 45.5622 261.089 45.2412C261.048 44.6007 260.966 43.7142 260.806 42.6619C260.49 40.5806 259.847 37.7257 258.513 34.8075C257.175 31.8807 255.075 28.7483 251.782 26.3477C248.437 23.9099 244.15 22.4402 238.845 22.4402ZM226.924 41.5881H226.855C227.103 40.682 227.43 39.7311 227.858 38.7958C228.709 36.9354 229.893 35.2804 231.526 34.0904C233.108 32.9377 235.391 32.0139 238.845 32.0139C242.299 32.0139 244.581 32.9377 246.162 34.0901C247.794 35.2797 248.977 36.9342 249.827 38.7942C250.255 39.7299 250.581 40.6816 250.83 41.5881H250.782H250.71H250.638H250.564H250.491H250.417H250.343H250.269H250.194H250.119H250.044H249.968H249.892H249.816H249.739H249.662H249.585H249.507H249.429H249.351H249.273H249.194H249.115H249.036H248.956H248.876H248.796H248.716H248.635H248.554H248.472H248.391H248.309H248.227H248.144H248.062H247.979H247.896H247.813H247.729H247.645H247.561H247.476H247.392H247.307H247.222H247.136H247.051H246.965H246.879H246.793H246.706H246.62H246.533H246.446H246.358H246.27H246.183H246.095H246.007H245.918H245.83H245.741H245.652H245.563H245.474H245.384H245.294H245.204H245.115H245.024H244.934H244.843H244.753H244.662H244.571H244.479H244.388H244.297H244.205H244.113H244.021H243.929H243.837H243.744H243.652H243.559H243.466H243.373H243.281H243.187H243.094H243.001H242.907H242.813H242.719H242.626H242.532H242.438H242.343H242.249H242.155H242.06H241.966H241.871H241.776H241.681H241.587H241.491H241.396H241.301H241.206H241.111H241.016H240.92H240.825H240.729H240.633H240.538H240.442H240.346H240.251H240.155H240.059H239.963H239.867H239.771H239.675H239.579H239.483H239.387H239.291H239.194H239.098H239.002H238.906H238.809H238.713H238.617H238.521H238.425H238.328H238.232H238.136H238.04H237.943H237.847H237.751H237.655H237.559H237.463H237.367H237.271H237.175H237.079H236.983H236.887H236.791H236.695H236.6H236.504H236.408H236.312H236.217H236.122H236.026H235.931H235.836H235.74H235.645H235.55H235.455H235.36H235.265H235.171H235.076H234.981H234.887H234.793H234.698H234.604H234.51H234.416H234.322H234.228H234.135H234.041H233.948H233.855H233.761H233.668H233.576H233.483H233.39H233.298H233.205H233.113H233.021H232.929H232.837H232.745H232.654H232.563H232.471H232.38H232.289H232.198H232.108H232.018H231.927H231.837H231.748H231.658H231.568H231.479H231.39H231.301H231.212H231.124H231.035H230.947H230.859H230.771H230.684H230.597H230.51H230.423H230.336H230.25H230.163H230.077H229.992H229.906H229.821H229.735H229.65H229.566H229.482H229.397H229.314H229.23H229.147H229.063H228.981H228.898H228.816H228.733H228.652H228.57H228.489H228.408H228.327H228.247H228.167H228.087H228.007H227.928H227.849H227.77H227.692H227.613H227.536H227.458H227.381H227.304H227.227H227.151H227.075H227H226.924Z" fill="white"/> <path d="M276.832 70.2393C275.542 68.8663 274.896 67.2125 274.896 65.2779C274.896 63.3433 275.542 61.7208 276.832 60.4102C278.184 59.0373 279.812 58.3508 281.717 58.3508C283.621 58.3508 285.25 59.0373 286.601 60.4102C287.953 61.7208 288.629 63.3433 288.629 65.2779C288.629 67.2125 287.953 68.8663 286.601 70.2393C285.25 71.6122 283.621 72.2987 281.717 72.2987C279.812 72.2987 278.184 71.6122 276.832 70.2393ZM341.833 71.5498H332.433V64.9035C328.377 69.8336 322.94 72.2987 316.119 72.2987C311.02 72.2987 306.78 70.8321 303.401 67.899C300.083 64.9659 298.424 61.0655 298.424 56.1978C298.424 51.2677 300.206 47.5857 303.769 45.1518C307.333 42.718 312.156 41.5011 318.239 41.5011H331.603V39.6289C331.603 33.0138 327.978 29.7062 320.728 29.7062C316.181 29.7062 311.45 31.3912 306.534 34.7612L301.926 28.2085C307.886 23.4032 314.645 21.0005 322.202 21.0005C327.978 21.0005 332.678 22.4983 336.303 25.4938C339.99 28.4269 341.833 33.0762 341.833 39.4417V71.5498ZM331.511 53.0151V48.8026H319.898C312.464 48.8026 308.746 51.1741 308.746 55.917C308.746 58.3508 309.668 60.223 311.511 61.5336C313.354 62.7817 315.904 63.4057 319.161 63.4057C322.479 63.4057 325.367 62.4697 327.824 60.5975C330.282 58.7253 331.511 56.1978 331.511 53.0151ZM366.525 71.5498H356.203V21.7494H366.525V71.5498ZM356.848 11.8268C355.558 10.5162 354.913 8.95609 354.913 7.1463C354.913 5.33651 355.558 3.77635 356.848 2.46581C358.138 1.15527 359.675 0.5 361.456 0.5C363.238 0.5 364.774 1.15527 366.065 2.46581C367.355 3.77635 368 5.33651 368 7.1463C368 8.95609 367.355 10.5162 366.065 11.8268C364.774 13.1373 363.238 13.7926 361.456 13.7926C359.675 13.7926 358.138 13.1373 356.848 11.8268Z" fill="#7666F6"/> </svg> </div> </div> <script> if (document.head) { let link = document.createElement('link'); link.rel = 'icon'; link.href = '/favicon.ico'; document.head.appendChild(link); } </script> Let's set up dependencies and check that everything works: TypeScript npm i copy-webpack-plugin -D npm run build npm run start NOTE: I recommend running npm run start in a separate terminal. Instead of npm run build, you can use npm run watch to speed up rebuilds during making changes. Conclusion In this part, we’ve built the foundation of a custom IDE with Theia: added VS Code extensions for syntax and themes, pulled in core Theia plugins, wired up a custom Vite-based UI, and even introduced our own splash screen. At this stage, you already have a functional browser-based IDE that can be launched and used for real work. But this is just the groundwork. The real transformation begins when we start stripping away the unnecessary parts, reshaping the interface, and building a UX tailored to our product. In the next part, I’ll show how to go deeper — rewiring tabs, sidebars, and layouts to turn Theia into something uniquely yours.

By Maksim Kachurin
Transforming Your Node.js REST API into an AI-Ready MCP Server
Transforming Your Node.js REST API into an AI-Ready MCP Server

The evolution of large language models (LLMs) and agentic AI requires a fundamental shift in how applications expose their capabilities. Traditional REST APIs are designed for software-to-software communication, requiring developers to read documentation and write custom integration code. The Model Context Protocol (MCP) is an open standard designed to solve this by creating a unified, machine-readable interface that AI agents can dynamically discover and interact with. This article provides a comprehensive guide on converting an existing Node.js REST API into an MCP server using the official TypeScript SDK, focusing on the architectural changes and crucial use cases that this conversion unlocks. The Paradigm Shift: From REST to MCP REST APIs are typically designed with human developers in mind, optimizing for resource management (CRUD operations) via HTTP verbs, path variables, and specific request/response formats. The MCP model, in contrast, is AI-first: AspectREST API (Traditional)MCP Server (AI-FIRST) Primary Consumer Human Developers, Client Applications AI Agents, LLMs, AI-Powered IDEs Interface HTTP verbs, Path, Query Params, Custom Body Standardized JSON-RPC messages (Tools, Resources, Prompts) Discovery Manual via OpenAPI/Swagger Documentation Dynamic via the list_tools() or list_resources() protocol Functionality Granular, atomic endpoints (GET /users/{id}) High-level, semantic actions (manage_user_profile) The conversion is not a direct port; it's an abstraction. You wrap your existing Node.js business logic with an MCP layer that translates the standardized MCP calls into the REST requests your API understands. Step 1: Set Up the Node.js MCP Environment The official Model Context Protocol TypeScript SDK is the core tool for this conversion. 1. Initialize the Project and Install Dependencies Assuming a basic Node.js (v18+) project, you'll need the MCP SDK, a utility for request validation (like Zod), and an HTTP client (like axios or node-fetch) to interact with your existing REST API. Shell npm init -y npm install @modelcontextprotocol/sdk zod node-fetch npm install -D typescript @types/node ts-node 2. Instantiate the MCP Server Create a file (e.g., mcp-server.ts) to set up the server instance and a transport layer, such as StdioServerTransport for local testing or StreamableHttpServerTransport for remote deployment. TypeScript import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; // Instantiate the core MCP server const server = new McpServer({ name: "MyNodeAPIServer", version: "1.0.0", capabilities: { tools: {}, resources: {}, prompts: {} }, }); // The transport handles communication with the LLM client const transport = new StdioServerTransport(); async function startServer() { // [Tool and Resource registration will go here] await server.connect(transport); console.log("MCP Server is running on standard I/O..."); } startServer().catch(console.error); Step 2: Curate and Define MCP Tools This is the most critical step. Instead of blindly exposing every REST endpoint, you must curate the functionality into high-level, agent-friendly Tools and Resources. 1. Designing LLM-Friendly Tools LLMs perform better with semantic, intention-based tools rather than granular, low-level API calls. Bad (REST-centric): get_user_by_id, update_user_name, update_user_emailGood (MCP-centric): manage_user_profile(userId, newName, newEmail) The MCP tool handler should orchestrate the necessary multiple REST calls to fulfill the single, high-level action. 2. Implementing the Tool Handler Each tool requires a descriptive name, a thorough natural language description for the LLM, and structured input/output schemas using Zod. TypeScript // Define the schema for the tool's input arguments const UpdateUserSchema = z.object({ userId: z.string().describe("The unique ID of the user to update."), newEmail: z.string().email().optional().describe("The user's new email address."), newSubscriptionPlan: z.enum(['basic', 'premium', 'pro']).optional().describe("The new subscription plan to apply."), }); server.registerTool( "manage_subscription", { title: "Manage User Subscription and Profile", description: "Updates a user's email address and/or changes their subscription plan. Requires the user's ID.", argsSchema: UpdateUserSchema, outputSchema: z.object({ status: z.string(), updatedFields: z.array(z.string()), }), }, async (args) => { const { userId, newEmail, newSubscriptionPlan } = args; const updatedFields: string[] = []; // --- REST API CALL Orchestration --- const REST_API_BASE = process.env.REST_API_URL; if (newEmail) { // 1. Call the REST API to update email await fetch(`${REST_API_BASE}/users/${userId}/email`, { method: 'PUT', body: JSON.stringify({ email: newEmail }), headers: { 'Content-Type': 'application/json' }, }); updatedFields.push('email'); } if (newSubscriptionPlan) { // 2. Call the REST API to update subscription await fetch(`${REST_API_BASE}/billing/${userId}/plan`, { method: 'POST', body: JSON.stringify({ plan: newSubscriptionPlan }), headers: { 'Content-Type': 'application/json' }, }); updatedFields.push('subscriptionPlan'); } // Return a structured response for the LLM return { status: "Success", updatedFields: updatedFields.length > 0 ? updatedFields : ["No changes made."], }; } ); 3. Creating Resources for Context For simple GET requests that provide context (read-only data), use ResourceTemplates. These allow the LLM to understand what data is available without necessarily calling a tool. TypeScript server.registerResource( "product_catalog_item", { title: "Product Catalog Item", description: "A single item from the product catalog, including price, stock, and description.", uriTemplate: "api://my-node-api-mcp/products/{productId}", dataSchema: z.object({ id: z.string(), name: z.string(), price: z.number(), description: z.string(), }), }, async (uri) => { // Parse the productId from the URI or argument const productId = uri.split('/').pop(); // Call your REST API: GET /products/{productId} const response = await fetch(`${process.env.REST_API_URL}/products/${productId}`); return await response.json(); } ); Step 3: Implement Security and Error Handling Security is paramount when exposing capabilities to an autonomous agent. 1. Authentication Integration Your MCP server acts as a proxy. Its internal HTTP client must handle authentication for the original REST API. This often involves securely loading API keys or OAuth tokens from environment variables and including them in the Authorization headers of your fetch or axios calls within the tool handlers. 2. Robust Error Responses AI agents rely on structured output to determine the success or failure of an action. Your handler must catch HTTP errors from the REST API and convert them into clear, structured MCP error responses. Bad: Throwing a raw HTTP 404 error.Good: Returning an MCP output with { status: "Error", message: "User with ID 123 not found in the database." } Key Use Cases Unlocked by MCP Converting to MCP is a strategic move that enables new classes of AI-powered applications. 1. AI-Powered Developer Tools (The "Cursor" Use Case) Many modern AI IDEs and code assistants (like Cursor, GitHub Copilot) use MCP to allow the AI to interact with the local development environment or internal services. Scenario: A developer asks, "Run the integration tests for the new user-management module."MCP Tool: run_npm_script(scriptName: string)Node.js API Logic: The tool executes a shell command like npm run test:user-management, securely, with the user's explicit approval. 2. Intelligent Customer Support Automation Expose your core CRM, inventory, or billing APIs as MCP tools to an internal AI agent. Scenario: A support agent asks the AI, "What is the order status for customer Alice, and can we apply a 10% discount?"MCP Tool 1 (Resource): get_customer_order_history(customerId)MCP Tool 2 (Tool): apply_discount_to_order(orderId, percentage)Benefit: The AI autonomously chains the calls, fetching the data and executing the action without manual steps. 3. Dynamic Workflow and Microservice Orchestration An MCP server can sit as an abstraction layer over a sprawling microservice architecture, allowing an LLM to orchestrate complex, multi-step workflows with a single semantic command. Scenario: The LLM receives the instruction, "Process a new customer onboarding for Jane Doe."MCP Tool: onboard_new_customer(name, email) Orchestration Logic: The tool's handler internally calls the User Microservice (REST POST), the Billing Service (REST POST), and the Email Service (REST POST), ensuring the entire business process is completed correctly. This makes the LLM integration simple and resilient to backend complexity. Conclusion: A Future of Standardized AI Integration Converting your Node.js REST API to support MCP is an investment in future-proofing your application for the age of autonomous AI agents. While simply wrapping every endpoint is a good starting point, the true power of MCP is realized through aggressive curation, designing high-level semantic tools that reflect user intent rather than API structure. This process transforms your API from a static data exchange service into a dynamic, AI-callable skillset, greatly expanding its utility in agentic ecosystems

By Lakshmi Narayana Rasalay
Converting ActiveMQ to Jakarta (Part III: Final)
Converting ActiveMQ to Jakarta (Part III: Final)

Advanced Technical Approach Some Java frameworks have taken on the complexity of supporting both javax and jakarta package namespaces simultaneously. This approach makes sense for frameworks and platform services, such as Jetty and ActiveMQ, where the core development team needs to move the code base forward to support newer JDKs, while also providing a way for application developers to adopt Jakarta EE gradually. This simplifies the support for open-source frameworks, as there are fewer releases to manage, and in the event of a security bug, being able to release one mainline branch vs having to go back and backport across past versions. However, supporting both javax and jakarta namespaces simultaneously in a single application is complicated and time-consuming. Additionally, it opens additional scenarios that may lead to errors and security gaps for enterprise applications. This limits the ability to set up verification checks and source code scanning to block pre-Jakarta libraries from being used or accidentally pulled in through transitive dependencies. It creates a lot of ambiguity and reduces the effectiveness of DevOps teams in providing pre-approved SDKs to be used by enterprise developers. With the pitfalls outweighing the benefits, enterprise projects should not need to support both javax and jakarta namespaces simultaneously in most scenarios. Special Consideration for Exception Handling for Remote Operations The one caveat to this best practice for enterprise applications is that there may be a need to support mapping exceptions between javax and jakarta package namespaces to support clients making remote calls to a service or API. The server-side either needs to be able to detect javax clients and translate, or a thin client-side wrapper is needed to handle any jakarta exceptions received by remote services. Apache ActiveMQ handles exception namespace mapping appropriately for all client release streams (starting with v6.1.0, v5.19.0, v5.18.4, v5.17.7, and v5.16.8), so no additional handling is required by applications when using Jakarta Messaging. Jakarta EE Updates and Nothing Else A key factor to ActiveMQ’s success was that the scope of change was limited to only what was necessary for the upgrade to Jakarta EE. The change of underlying frameworks naturally brought new minimum JDK version requirements and other changes, as Jakarta EE specifications brought forward their own set of changes. No protocol changes, no data format, or configuration changes were made to ActiveMQ to support backwards compatibility with javax clients and to support roll-forward and rollback during upgrades. Developers should resist the urge to tackle other refactoring, data model, or business functionality changes when making the upgrade to Jakarta EE. These upgrades should be structured as technical debt-only releases to ensure the best outcomes. Jakarta Migration Planning Guide Team Impact: Organizing Changes for Code Review For an enterprise taking on a similar migration of a large and established code base, I highly recommend following this next piece of advice to lower the time and level of effort. Enforce an organizational policy that requires git commits related to package naming to be separated. There should be two types that are clearly labeled in the comments: Java package import namespace-only changesCode changes Namespace-only changes involve updating the file from “import javax.” to “import jakarta.” These text changes may live in Java code files, Spring XML, config properties, or other non-Java code artifacts used by the application. Code changes are updates required due to fixes, technical debt, supporting Jakarta EE specification changes, or framework API changes (such as Spring or Jetty). By separating these changes, you will greatly reduce the time required to review and approve changes. Java package namespace-only changes will have hundreds to thousands of files changed, and thousands to tens of thousands of lines changed. For the most part, these changes can be approved quickly, without the need for a deep code review. The actual impacting code changes should impact fewer files and fewer lines of code change. The code reviews on these changes will require a closer look, and by reducing the scope, you will greatly reduce the time required for code reviews. Practical Tips for Jakarta Migration Drop end-of-life and deprecated modules from your code base.Migrate or drop end-of-life and deprecated dependencies.Upgrade code to use in-Java features where commons-* dependencies are no longer needed.Upgrade to current non-Jakarta affecting dependencies you may have been putting off (Log4j v2, JUnit v5, etc.).Where possible, release JDK 17 changes first (Upgrade JDK using LTS versions 8 -> 11 -> 17).Release a tech-debt update of your product or application. This allows for supporting two modern release streams—non-Jakarta and Jakarta.Update frameworks to Jakarta EE versions.Break-up commits to have import-only changes for faster review.For complex in-house ‘framework’ type components, consider releasing support for both javax and jakarta at the same time.Add support for client-side Jakarta EE module alongside existing modules in the javax release stream.Break-up commits to have import-only changes for faster reviews. In Summary Apache ActiveMQ was successful in its migration to Jakarta EE by tackling necessary technical debt and putting off the urge to incorporate too many changes. The transition was successful, and users were able to quickly adopt the ActiveMQ 6.x releases in their Jakarta EE projects. Additionally, since the wire protocol, configuration, and data formats did not change, older javax applications (and non-Java applications) were able to work seamlessly through an upgrade. This is an exciting time for Java developers as the ecosystem is rapidly adopting awesome new features and great language improvements. I’m interested in your feedback as you tackle Jakarta EE and JDK upgrades for projects of all sizes. Reference Material change typeestimated level of effortNamespace change from “import javax…” to “import jakarta..”LowUpgrade to JDK 17MediumUpdate Maven tooling to align with JDK 17MediumUpdate and refactor code to use updated Jakarta specifications APIsMediumUpdate and refactor code to use current dependencies that implement updated specification APIsHighPay down technical debt HighUpdate and refactor code to drop any dependencies that are not current with Jakarta, JDK 17 or transitive dependencies HighTeam impacts - Managing change across the enterprise High ActiveMQ's Jakarta Migration Metrics The following statistics are provided as a reference for the level of effort required in migrating a medium-sized, mature Java project to Jakarta EE. PRs1Commits25 (the number of intermediate commits is over 100)Files changed1,425Lines added9,514Lines removed8,091Modules dropped2* (1 is the transition module, which got a relocation)Dependencies re-homed2Frameworks dropped2Deprecated J2EE specifications dropped1PR work tasks28CI build jobs80 Apache ActiveMQ 6.0.0 Jakarta Messaging 3.1.0 Release Summary Permanently dropped module: activemq-partition (drop deprecated Apache ZooKeeper test dependency)Jakarta APIs: Jakarta MessagingJakarta XMLJakarta ServletJakarta TransactionUpgrade key dependencies: Jetty v11Spring v6Java JDK 17+Maven modulesDrop JEE API specs that do not have a Jakarta version: j2ee-management (interfaces re-implemented locally to Apache ActiveMQ)Re-homed test dependencies: stompjms Java STOMP clientjoram-jms-tests JMS test utilitiesTemporarily dropped dependencies that did not have Jakarta support at the time. Note: Both have been added back in as of ActiveMQ 6.1.x. JolokiaApache Camel

By Matt Pavlovich
Developing Low-Cost AI-Based Similarity Search
Developing Low-Cost AI-Based Similarity Search

The world of artificial intelligence (AI) and large language models (LLMs) often conjures images of immense computing power, proprietary platforms, and colossal GPU clusters. This perception can create a high barrier to entry, discouraging curious developers from exploring the fundamentals. I recently embarked on a project — a sophisticated yet simple AI-powered chatbot I call the Wiki Navigator — that proves this complexity is often unnecessary for learning the essentials. By focusing on core concepts like tokenization, vector embeddings, and cosine similarity, I built a functional RAG (retrieval-augmented generation) search solution that operates across 9,000 documents in the Chromium open-source codebase. It took me a few hours to run, and the next day, I was able to reuse the same codebase to train a chatbot on open-source books about the Rust programming language to have useful help during my Rust learning journey. The main revelation? You don't need to dive too deep with huge GPU cards to learn the essentials of LLM and AI. It is a supremely rewarding and practical experience to learn by doing, immediately yielding results without incurring significant expense. The Magic of Vector Embeddings Our Wiki Navigator functions not by generating novel text, but by reliably retrieving contextual replies and relevant links from source documentation, preventing hallucination by strictly following the links in the wiki. It is essentially a contextual search engine powered by retrieval-augmented generation (RAG). The core concept is surprisingly straightforward: Training phase: Convert all your documents (like Q&A pairs and wiki content) into a digital representation known as vector embeddings (watch this great explanation if you haven't yet). This process, which can take an hour or so for large corpora, creates a vector index.Querying (query phase): When a user submits a question, that query is also converted into a vector embedding.Comparison: The system compares the query vector against the document vectors using the Cosine Similarity operation to find the closest matches. If we found two vectors near each other, that most likely means a match in terms of the context (though, as we can see later, not always). Principal diagram This simple process works effectively for tasks like navigating documentation and finding relevant resources. Ensuring Algorithmic Parity While many articles focus on the theory of similarity search, the real fun lies in implementing it. Interestingly enough, to run a simplistic MVP, you take NO AI MODEL, which makes it possible to be deployed statically, running entirely in the browser, making it perfect for hosting on platforms like GitHub Pages. This static deployment requires the training application (C#) and the client application (JavaScript) to share identical algorithms for tokenization and vector calculation, ensuring smooth operation and consistent results. The training pipeline, which prepares the context database, is built in C# (located in TacTicA.FaqSimilaritySearchBot.Training/Program.cs). During training, data is converted into embeddings using services like the SimpleEmbeddingService (hash-based, in case of NO AI model for static web site deployment), the TfIdfEmbeddingService.cs (TF-IDF/Keyword-Based Similarity - an extended version of trainer), or the sophisticated OnnxEmbeddingService (based on the pre-trained all-MiniLM-L6-v2 transformer model, which would require you to run some good back-end with an AI model loaded into RAM). Deployment schemes In this article, I mainly focus on the first option — a simplistic hash-based approach. I also have an AI-model-based solution running in production, for example, in a Rust similarity search example. This is a full-fledged React application running all comparisons on the back-end, but the fundamental concepts stay the same. The core mathematical utilities that define tokenization and vector operations reside in C# within TacTicA.FaqSimilaritySearchBot.Shared/Utils/VectorUtils.cs. To ensure the client-side browser application running in JavaScript via TacTicA.FaqSimilaritySearchBot.Web/js/chatbot.js (or TacTicA.FaqSimilaritySearchBot.WebOnnx/js/chatbot.js for the AI-model based one) can process new user queries identically to C# training algorithm, we must replicate those crucial steps. It is also critical to make sure that all calculations produce the same outputs in both C# and JavaScript, during both training and running, which might take additional efforts, but is still pretty straightforward. For example, these two: From SimpleEmbeddingService.cs: JavaScript // This method is similar to one from chatbot.js private Func<double> SeededRandom(double initialSeed) { double seed = initialSeed; return () => { seed = (seed * 9301.0 + 49297.0) % 233280.0; return seed / 233280.0; }; } From chatbot.js: JavaScript // Seeded random number generator seededRandom(seed) { return function() { seed = (seed * 9301 + 49297) % 233280; return seed / 233280; }; } C# Training Example: Vector Utility In the C# training application, the VectorUtils class is responsible for calculating cosine similarity, which is the heart of the comparison operation: JavaScript // Excerpt from TacTicA.FaqSimilaritySearchBot.Shared/Utils/VectorUtils.cs // This function calculates how 'similar' two vectors (embeddings) are. public static double CalculateCosineSimilarity(float[] vectorA, float[] vectorB) { // [C# Implementation Detail: Normalization and dot product calculation // to determine similarity score between 0.0 and 1.0] // ... actual calculation happens here ... // return similarityScore; } Running the training set will take an hour, because we are not using GPUs, parallelization, or any other fancy stuff, and because we are still learning the basics and do not want to overcomplicate things for now: Watch the full video here. JavaScript Client Example: Real-Time Search The client application must then perform the same calculation in real time for every user query against the pre-computed index. The system relies on fast in-memory vector search using this very simplistic algorithm. JavaScript // Excerpt from TacTicA.FaqSimilaritySearchBot.Web/js/chatbot.js // This function is executed when the user submits a query. function performSimilaritySearch(queryVector, documentIndex) { let bestMatch = null; let maxSimilarity = 0.0; // Convert user query to vector (if using the simple hash/TF-IDF approach) // or use ONNX runtime for transformer model encoding. // Iterate through all pre-calculated document vectors for (const [docId, docVector] of Object.entries(documentIndex)) { // Ensure the JS implementation of Cosine Similarity is identical to C#! const similarity = calculateCosineSimilarity(queryVector, docVector); if (similarity > maxSimilarity) { maxSimilarity = similarity; bestMatch = docId; } } // Apply the configured threshold (default 0.90) for FAQ matching. if (maxSimilarity >= CONFIG.SimilarityThreshold) { // [Action: Return FAQ Response with Citation-Based Responses] } else { // [Action: Trigger RAG Fallback for Full Document Corpus Search] } return bestMatch; } By ensuring that the underlying vector utilities are functionally identical in both C# and JavaScript, we guarantee that the query result will be consistent, regardless of whether the embedding was calculated during the training phase or the real-time query phase. Watch the full GIF here. As you can see, it doesn’t take long to have a running app. Beyond the Simple Lookup Our bot is far more sophisticated than a simple keyword search. It is engineered with a three-phase architecture to handle complex queries: Phase 1: Context database preparation. This is the initial training where Q&A pairs and document chunks are converted to vectors and stored in an index.Phase 2: User query processing. When a query is received, the system first attempts Smart FAQ Matching using the configured similarity threshold (default: 0.90). If the confidence score is high, it returns a precise answer.Phase 3: General knowledge retrieval (RAG fallback). If the FAQ match confidence is low, the system activates RAG Fallback, searching the full document corpus, performing Top-K retrieval, and generating synthesized answers with source attribution. This sophisticated fallback mechanism ensures that every answer is citation-based, providing sources and confidence scores. Depending on the use cases, you can switch ON or OFF citations, as the quality of response hugely depends on the number of questions and answer pairs you used during training. A low amount of Q&A would make this bot find irrelevant citations more frequently. Thus, if you simply don't have enough Q&A, the bot still can be useful by returning valid URL links, but not citations. With a good amount of Q&A, you can notice the quality of answers getting higher and higher. The Nuances of Similarity Search This hands-on exploration immediately exposes fascinating, practical insights that often remain hidden in theoretical papers. For instance, comparing approaches side-by-side reveals that the bot can operate both with an AI model (using the transformer-based ONNX embedding) and even without it, leveraging pure hash-based embeddings. While the hash-based approach is simple, the efficacy of embeddings, even theoretically, is limited, as discussed in the paper "On the Theoretical Limitations of Embedding-Based Retrieval." Furthermore, working directly with cosine similarity illuminates concepts like "Cosine Similarity Abuse" — a fun, practical demonstration of how one can deliberately trick non-intelligent AI systems. This is only a scratch of the surface of the bigger "Prompt Injection" problem (example good reading) that truly poses a serious threat to the users of AI and software engineers who build AI for production use. Your Next AI Project Starts Now Building a robust, functional bot that handles 9,000 documents across a complex project like Chromium requires technical diligence, but it does not require massive infrastructure. This project proves that the fundamental essentials of LLM and AI — tokenization, vectorization, and similarity comparison — are perfectly accessible to anyone willing to dive into the code. The Wiki Navigator serves as a powerful demonstration of what is possible with similarity search on your own internal or corporate data. I encourage you to explore the open-source code and see how quickly you can achieve tangible results: Source codeChromium similarity search demoRust similarity search demo This is just the beginning. Future explorations can dive deeper into topics like advanced vector search techniques, leveraging languages like Rust in AI tooling, and optimizing AI for browser-based applications. Start building today!

By Anton Yarkov DZone Core CORE
Pattern Searching and PostgreSQL Full-Text Search: Understanding the Mismatch
Pattern Searching and PostgreSQL Full-Text Search: Understanding the Mismatch

A while ago, a request emerged to implement a global find functionality in one of our products. Briefly, the purpose was to be able to search for multiple (different) entities by a pattern that is looked up and matched against several of their attributes. Not very complicated at first glance, such a feature comes with a lot of concerns and trade-offs that need to be analyzed, in addition to the implementation itself, in order to ensure at least a pleasant user experience. As the underlying database was PostgreSQL and I was aware of its powerful full-text search (FTS) capability, I considered it a potentially good candidate, so I started researching it, trying things out, aiming to compile the solution around it. Unfortunately for the requirement, but fortunately for me and for the interesting FTS capabilities I became aware of during this study, a few hours later, I understood why it cannot be used for pattern searching. The purpose of this article is to understand a few of the PostgreSQL FTS basics, use them in a concrete example while trying to perform pattern searching, observe the results, and understand why it isn’t a solution for such a case. We will jump right into it and make the important statements first, then detail and implement the proof of concept. Why Pattern Searching Doesn’t Work With FTS PostgreSQL full-text search is designed for linguistics-based search (words, phrases, ranking by relevance) and not for pattern-based search (wildcards, substrings, regex). The two serve fundamentally different purposes. A few of the cons of using FTS in this scenario: Tokenization and Dictionaries FTS breaks text into lexemes (normalized word units), which means searching is word-based (or phrase-based), not character-by-character. For example, searching for "cat" won’t match "concatenate," because "concatenate" is stored as the lexeme 'concaten':1. No Wildcard or Substring Matching Pattern matching (LIKE, ILIKE, regex) works on raw text, while FTS does not. FTS queries only match entire words or their stems, not arbitrary substrings ('%cat%') or regex-like patterns. Loss of Word Boundaries Once tokenized, FTS discards original text formatting (whitespace, punctuation, order to some extent). This makes it impossible to reliably search for patterns across boundaries like "abc-def" or "foo_bar." Focus on Semantics, Not Syntax FTS is built to mainly answer this question: "What documents are most relevant to these words?" It prioritizes ranking, stemming, stop word removal, and linguistic analysis. Pattern search, on the other hand, is purely syntactic and exact. Indexing Mismatch FTS uses GIN or GIST indexes optimized for word lookups. Pattern searching requires B-tree indexes for faster substring/regex matches. To a certain extent, mixing the two is inefficient. No Regular Expression Support FTS query operators (@@, to_tsquery) accept logical combinations of lexemes (AND, OR, NOT, FOLLOWS), not regex. You can't, for example, run to_tsquery('%cat%') or similar and expecting wildcard behavior. PostgreSQL Full-Text Search Concepts Before the concrete example, a few of the basic concepts are defined briefly. A document is the actual content that is searched for. For instance, it may designate one or more text columns in a table, or textual data of interest gathered from multiple tables. In order for the documents to be searchable, they must be transformed appropriately into a form called tsvector. to_tsvector(regconfig, text) is the function that creates this PostgreSQL's internal representation of a document that is optimized for performing the search operation. SQL select to_tsvector('english', 'PostgreSQL database results if you concatenate Postgre and SQL.'); outputs: Plain Text 'concaten':6 'databas':2 'postgr':7 'postgresql':1 'result':3 'sql':9 It is a sorted list of normalized words together with their position in the initial document. In the above example, the items are sorted alphabetically. The normalized words are called lexemes. A lexeme basically represents the meaning of a word in a full-text search context. Usually, when a phrase is transformed into lexemes, the stop words are removed, all items are lowercased, special characters are handled, and only the root form of the words is kept (stemming). Once the documents are transformed, they may be searched using the tsquery type, a search query that can be applied on tsvectors. SQL select to_tsquery('english', 'PostgreSQL & database'); outputs: Plain Text 'postgresql' & 'databas' The supported operators are &, |, !, and <->. While the first three are intuitive, the last means follows, more exactly, the document is matched if the two lexemes come right one after the other. A Practical Experiment Considering the PostgreSQL Database Server is up and running, one may create a simple schema that is used in this use case. SQL CREATE SCHEMA pgfts; To keep things simple, only one table is used, a table that stores inventory items. Such an entity has a number, a service, is from a vendor, and comments can be recorded for each. SQL DROP TYPE IF EXISTS vendor; CREATE TYPE vendor AS ENUM ('Vodafone', 'Orange'); DROP TYPE IF EXISTS service; CREATE TYPE service AS ENUM ('VOIP', 'MPLS'); DROP TABLE IF EXISTS inventory; CREATE TABLE IF NOT EXISTS inventory ( id SERIAL PRIMARY KEY, number TEXT NOT NULL, service service NOT NULL, vendor vendor NOT NULL, comments TEXT ); In order to be able to experiment with searching, first, some data is created — a few inventory items for each combination of services and vendors are inserted. SQL INSERT INTO inventory (number, service, vendor, comments) SELECT 'inv' || floor(random() * 100000) || 'vdf', 'VOIP', 'Vodafone', 'For inventory ' || i || ' from Vodafone: vdf voip.' FROM generate_series(1, 3) i; INSERT INTO inventory (number, service, vendor, comments) SELECT 'inv-' || floor(random() * 100000) || 'vdf', 'MPLS', 'Vodafone', 'For inventory ' || i || ' from Vodafone: vdf mpls.' FROM generate_series(1, 3) i; INSERT INTO inventory (number, service, vendor, comments) SELECT 'inv' || floor(random() * 100000) || '-org', 'VOIP', 'Orange', 'For inventory ' || i || ' from Orange: org voip.' FROM generate_series(1, 3) i; INSERT INTO inventory (number, service, vendor, comments) SELECT 'inv-' || floor(random() * 100000) || '-org', 'MPLS', 'Orange', 'For inventory ' || i || ' from Orange: org mpls.' FROM generate_series(1, 3) i; The form of the inventory numbers was intentionally generated differently for certain entities, as we’ll shortly see. The raw data looks as shown in the picture below. The intention of the experiment is to perform a pattern search and match patterns from the number column in a full-text search manner. We assume the full search is performed on both the number and comments columns. In order to be able to accomplish that, an additional column containing the tsvector representation of the two is added. Moreover, a GIN index is created as well, although the performance is not a concern here. SQL ALTER TABLE inventory DROP COLUMN IF EXISTS search_ts_vector; ALTER TABLE inventory ADD COLUMN IF NOT EXISTS search_ts_vector tsvector GENERATED ALWAYS AS ( to_tsvector('english', coalesce(number, '')) || to_tsvector('english', coalesce(comments, '')) ) STORED; CREATE INDEX inventory_search_idx ON inventory USING GIN (search_ts_vector); The documents look as follows, only the columns of interest are shown. Plain Text +--+-------------+----------------------------------------+---------------------------------------------------------------------------------------+ |id|number |comments |search_ts_vector | +--+-------------+----------------------------------------+---------------------------------------------------------------------------------------+ |1 |inv92615vdf |For inventory 1 from Vodafone: vdf voip.|'1':4 'inv92615vdf':1 'inventori':3 'vdf':7 'vodafon':6 'voip':8 | |2 |inv88528vdf |For inventory 2 from Vodafone: vdf voip.|'2':4 'inv88528vdf':1 'inventori':3 'vdf':7 'vodafon':6 'voip':8 | |3 |inv6924vdf |For inventory 3 from Vodafone: vdf voip.|'3':4 'inv6924vdf':1 'inventori':3 'vdf':7 'vodafon':6 'voip':8 | |4 |inv-80542vdf |For inventory 1 from Vodafone: vdf mpls.|'1':6 '80542vdf':3 'inv':2 'inv-80542vdf':1 'inventori':5 'mpls':10 'vdf':9 'vodafon':8| |5 |inv-81722vdf |For inventory 2 from Vodafone: vdf mpls.|'2':6 '81722vdf':3 'inv':2 'inv-81722vdf':1 'inventori':5 'mpls':10 'vdf':9 'vodafon':8| |6 |inv-90259vdf |For inventory 3 from Vodafone: vdf mpls.|'3':6 '90259vdf':3 'inv':2 'inv-90259vdf':1 'inventori':5 'mpls':10 'vdf':9 'vodafon':8| |7 |inv39708-org |For inventory 1 from Orange: org voip. |'1':6 'inv39708':2 'inv39708-org':1 'inventori':5 'orang':8 'org':3,9 'voip':10 | |8 |inv93571-org |For inventory 2 from Orange: org voip. |'2':6 'inv93571':2 'inv93571-org':1 'inventori':5 'orang':8 'org':3,9 'voip':10 | |9 |inv4770-org |For inventory 3 from Orange: org voip. |'3':6 'inv4770':2 'inv4770-org':1 'inventori':5 'orang':8 'org':3,9 'voip':10 | |10|inv-87130-org|For inventory 1 from Orange: org mpls. |'-87130':2 '1':6 'inv':1 'inventori':5 'mpls':10 'orang':8 'org':3,9 | |11|inv-25686-org|For inventory 2 from Orange: org mpls. |'-25686':2 '2':6 'inv':1 'inventori':5 'mpls':10 'orang':8 'org':3,9 | |12|inv-99591-org|For inventory 3 from Orange: org mpls. |'-99591':2 '3':6 'inv':1 'inventori':5 'mpls':10 'orang':8 'org':3,9 | +--+-------------+----------------------------------------+---------------------------------------------------------------------------------------+ Let's assume the pattern is '2vd' or '0-or'. In each case, we would expect to get two records back – ids 4, 5, and 9, 10 respectively. When we execute the following: SQL SELECT id, number, search_ts_vector, ts_rank(search_ts_vector, query) as search_ts_rank FROM inventory, to_tsquery('english', '2vd') query WHERE search_ts_vector @@ query ORDER BY search_ts_rank DESC; The result is as follows: Plain Text +--+------------+-------------------------------------------------------------------------------+--------------+ |id|number |search_ts_vector |search_ts_rank| +--+------------+-------------------------------------------------------------------------------+--------------+ |8 |inv93571-org|'2':6 'inv93571':2 'inv93571-org':1 'inventori':5 'orang':8 'org':3,9 'voip':10|0.12158542 | |1 |inv92615vdf |'1':4 'inv92615vdf':1 'inventori':3 'vdf':7 'vodafon':6 'voip':8 |0.06079271 | +--+------------+-------------------------------------------------------------------------------+--------------+ As the search_ts_vector column of these records contains lexemes starting with 'inv9', they are returned. Further, if we search by pattern 'vdf', SQL SELECT id, number, comments, search_ts_vector, ts_rank(search_ts_vector, query) as search_ts_rank FROM inventory, to_tsquery('english', 'vdf') query WHERE search_ts_vector @@ query ORDER BY search_ts_rank DESC; We indeed receive the first six records as a result, and this happens because lexemes having the 'vdf' value do exist for them. These lexemes were actually created from the content in the comments column. If the full-text search had been configured only for the number column, no results would have been returned. The pattern search limitation is clear and has a logical explanation now. On the other hand, one last query is worth executing, though, to show the power of PostgreSQL full-text search. SQL SELECT id, number, comments, search_ts_vector, ts_rank(search_ts_vector, query) as search_ts_rank FROM inventory, to_tsquery('english', 'inv:* & voip & orange') query WHERE search_ts_vector @@ query ORDER BY search_ts_rank DESC; We aim for all inventory entities whose numbers start with 'inv' and contain in their numbers or comments the words 'voip' and 'orange' or others starting with 'inv'. Quite a complex query, but the results are returned immediately. Plain Text +--+------------+--------------------------------------+-------------------------------------------------------------------------------+--------------+ |id|number |comments |search_ts_vector |search_ts_rank| +--+------------+--------------------------------------+-------------------------------------------------------------------------------+--------------+ |7 |inv39708-org|For inventory 1 from Orange: org voip.|'1':6 'inv39708':2 'inv39708-org':1 'inventori':5 'orang':8 'org':3,9 'voip':10|0.26071766 | |8 |inv93571-org|For inventory 2 from Orange: org voip.|'2':6 'inv93571':2 'inv93571-org':1 'inventori':5 'orang':8 'org':3,9 'voip':10|0.26071766 | |9 |inv4770-org |For inventory 3 from Orange: org voip.|'3':6 'inv4770':2 'inv4770-org':1 'inventori':5 'orang':8 'org':3,9 'voip':10 |0.26071766 | +--+------------+--------------------------------------+-------------------------------------------------------------------------------+--------------+ Takeaway By combining the theoretical aspects with the results of the simple experiment above, the conclusion is the following. PostgreSQL full-text search is great for semantic, language-aware searching, but it fails at raw pattern search because of tokenization, indexing strategy, and focus on relevance rather than on the exact matches. Nevertheless, to finish on an optimistic note, the capabilities of PostgreSQL full-text search are more than satisfactory and a very good alternative to Elasticsearch when it comes to implementing general searching solutions, especially in the case of small and medium-sized applications. Resources PostgreSQL FTS DocumentationThe picture is a mosaic detail from the Dome of St. Peter’s Basilica in the Vatican

By Horatiu Dan
The Agile AI Manifesto
The Agile AI Manifesto

TL;DR: The Agile Manifesto Predicted AI The Agile world is splitting into two camps: Those convinced AI will automate practitioners out of existence, and those dismissing it as another crypto-level fad. Both are wrong. The evidence reveals something far more interesting and urgent: Principles written in 2001, before anyone imagined GPT-Whatever, align remarkably well with the most transformative technology of recent years. This is not a coincidence. I believe it is proof that human-centric values transcend technological disruption; it is the Agile AI Manifesto. The Broken Debate Walk into any Agile community event today, and you will encounter two opposing camps, each equally confident and equally wrong: The AI maximalists predict the imminent automation of Scrum Masters, Product Owners, and Agile Coaches. Chatbots are replacing consultants, facilitation is reduced to prompts, and human judgment is rendered obsolete.The AI luddites, on the other side, dismiss the entire phenomenon as "just another metaverse," destined to join Web3 and NFTs in the technology graveyard. Both groups make the same error. They treat AI as either a complete solution or a complete fiction. They miss what is actually happening in organizations right now: a change that neither replaces human expertise nor fades into irrelevance. Why This Time Actually May Be Different Web3 and Crypto, replacing traditional finance, and the metaverse, redefining human interaction; both technologies were solutions desperately seeking problems. Seriously, at no time did Agile practitioners wake up thinking, "If only I had a blockchain-based smart contract for my Sprint Goal." Generative AI addresses problems that Agile practitioners already experience daily, such as analyzing and categorizing vast amounts of customer feedback, identifying patterns across Retrospectives, and detecting market shifts buried in noise. The problems existed first. AI provides solutions. In my consulting practice across European enterprises, Product Owners and Product Managers are using AI to complete discovery cycles several times faster, but only when they already know which questions to ask. Retrospectives that draw on AI-identified patterns across multiple Sprints surface systemic impediments that manual review often misses. These effects are not predictions; they are measurements. Dell'Acqua et al. (2025) confirmed this with 776 professionals at Procter & Gamble [1]. Individuals with AI matched the performance of entire teams without AI. Teams with AI were significantly more likely to produce top-tier solutions. Both AI-enabled groups worked 12-16% faster. The Alignment Nobody Expected Here is what makes the Agile Manifesto's relationship with AI genuinely remarkable. Principles articulated in 2001, before the advent of smartphones, cloud computing, and generative AI, align almost perfectly with how generative AI functions in 2024. The research confirms three patterns that are observable. These patterns extend beyond individual use cases and apply across Scrum, Kanban, and Extreme Programming: Pattern One: AI Enhances Preparation, Humans Make Decisions The human-in-the-loop approach: AI prepares the context, and humans decide. AI analyzes feedback continuously, surfaces latent needs, and synthesizes insights. But humans judge which insights matter and decide what to build. Without that expertise, AI-generated synthesis remains useless data. AI assists with code review and identifies complexity. However, humans ultimately determine whether software effectively solves customer problems. AI provides system insights. But teams navigate trade-offs and organizational constraints. The Manifesto's principle that "the best architectures, requirements, and designs emerge from self-organizing teams" remains intact, as decision-making stays with humans who understand context and relationships. Pattern Two: Continuous Feedback Goes from Aspiration to Operational Reality "Welcome changing requirements, even late in development" sounds impossible because detecting and responding to change requires enormous information processing. AI makes this Agile Manifesto principle operationally viable at scale. Customer collaboration becomes continuous rather than periodic when AI monitors behavioral patterns, sentiment, and support conversations in real-time. What required quarterly research cycles now happens weekly or daily: Market shifts surface through AI-powered pattern recognition across data sources too vast for manual analysis. Teams make adaptation decisions informed by a comprehensive analysis rather than a gut feeling. "Our highest priority is to satisfy the customer" shifts from well-meaning intent to operational reality when you discover what satisfies customers faster than competitors can. (Isn't that the essence of "Agile," learning faster than the competition?) Pattern Three: AI Makes Face-to-face Conversation Exponentially More Valuable The Agile Manifesto declares, "face-to-face conversation is the most efficient and effective method of conveying information." AI does not replace this. It makes it massively more valuable by handling information processing while humans focus on judgment, relationship-building, and collaborative decision-making. The Scrum Masters or Agile Coaches who arrive at stakeholder meetings with an AI-synthesized analysis of political positions do not replace conversation. They transform it from information gathering to strategic negotiation. AI can identify the top five pain points. It cannot read the room during a tense meeting and know when to push versus when to back off. That is your moat. Dell'Acqua et al. (2025) [1] found that people using AI reported higher positive emotions and lower negative emotions. When AI removes information processing burden, humans focus more effectively on relationship work. Teams using AI for customer analysis have richer conversations, not fewer. The Agile Manifesto got it right: human conversation is irreplaceable. The Contrarian Position Nobody's Voicing Here is what both camps miss: The biggest threat is not that AI replaces agile practitioners. It is AI that reveals what many organizations have suspected. They never needed Agile practitioners. They needed someone to manage Jira. If your value proposition is running ceremonies, I deliberately do not refer to them as "events," maintaining Product Backlogs, and generating burndown charts, AI reveals you were doing work the organization could have automated a decade ago. The separation is between practitioners who do real Agile work and those who perform Agile theater. AI is an expertise detector. Agile AI Manifesto: AI Cannot Read the Room (And That's Your Moat) AI can analyze support tickets and identify the top five pain points. It cannot read the room during a tense stakeholder meeting and knows when to push versus when to back off, as it lacks empathy and an understanding of the current company's politics and personal agendas of stakeholders. It cannot build psychological safety that allows teams to admit they do not understand the architecture. It cannot navigate organizational resistance by understanding which stakeholders need data, who needs stories, and who needs political cover. These capabilities (reading context, building trust, facilitating difficult conversations, navigating politics) are your moat. AI makes this separation absolute. A mediocre practitioner armed with AI remains mediocre, now producing mediocre outputs faster. An expert practitioner armed with AI becomes significantly more effective. Why "Good Enough" Agile Just Died An experienced Product Owner using AI can now test ten positioning hypotheses in the time previously required for one or two. A skilled Scrum Master can analyze team dynamics across six Retrospectives to identify systemic impediments that manual review would probably miss. The era of "good enough Agile" is coming to an end because "good enough" practitioners cannot fully leverage what AI offers. Organizations that recognize the value of this effect invest in structured AI capability development for expert practitioners. Expertise plus AI creates a competitive advantage. AI without expertise creates expensive noise. This reality drove overwhelming enrollment in my October cohort, with a waiting list comprising 6,000-plus peers. What to Do Monday Morning The strategic question, "Will AI amplify or replace me?", matters less than the tactical one: "What am I doing this week to ensure AI is amplifying my capabilities?" To get your reflection going: The Agile AI Manifesto for Scrum Masters Take your last three Retrospectives. Use AI to analyze transcripts or notes and identify patterns that you might have missed manually. Then design one facilitation experiment based on that insight. The value is not the AI analysis. It is whether you can translate it into better facilitation. If you cannot, the AI reveals a gap in your expertise. For Product Owners and Product Managers Take your last 100 customer support tickets or user feedback items. Use AI to synthesize patterns and identify the top five latent needs. Then spend one hour with your Developers discussing whether these needs align with your product strategy and how you would validate them. If you cannot lead that conversation effectively, the AI reveals you do not understand product discovery fundamentals. For Agile Coaches Take your current client engagement. Utilize AI to analyze available organizational data, including meeting patterns, communication flows, and decision-making dynamics. Identify one systemic impediment you had not previously surfaced. Then design one intervention to address it. If you cannot design that intervention, the AI reveals you are delivering playbooks, not coaching. The Agile Manifesto predicted this moment by understanding something timeless: technology serves people, not the other way around. The practitioners who thrive will be those who make that principle operational this week, not eventually. The question each practitioner should ask is simple: Will I dismiss this as hype, embrace it as replacement, or learn to wield it as amplification? Agile AI Manifesto Conclusion The Agile Manifesto remains relevant not despite AI, but because of it. The Manifesto authors got something right: Principles built on human needs survive technology changes. AI amplifies what you bring: Bring expertise, judgment, and the ability to handle human complexity; AI makes you more effective. Bring only mechanical competence, and AI shows you were always replaceable. Your choice. Choose wisely. References [1] Dell'Acqua, F., Ayoubi, C., Lifshitz-Assaf, H., Sadun, R., Mollick, E. R., Mollick, L., Han, Y., Goldman, J., Nair, H., Taub, S., & Lakhani, K. R. (2025). "The Cybernetic Teammate: A Field Experiment on Generative AI Reshaping Teamwork and Expertise." Harvard Business School Working Paper No. 25-043. Available at: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=5188231[2] Harvard Business School Digital Data Design Institute. (2025). "The Cybernetic Teammate: How AI is Reshaping Collaboration and Expertise in the Workplace." Available at: https://d3.harvard.edu/the-cybernetic-teammate-how-ai-is-reshaping-collaboration-and-expertise-in-the-workplace/[3] Mollick, E. (2025, March 22). "The Cybernetic Teammate." One Useful Thing. Available at: https://www.oneusefulthing.org/p/the-cybernetic-teammate

By Stefan Wolpers DZone Core CORE
A Brief Overview of Designing and Testing Effective Chatbots
A Brief Overview of Designing and Testing Effective Chatbots

As startups, enterprises, and individuals all around are looking to understand how they can incorporate chatbots into their systems for customer service, internal workflows, and compliance, it is incredibly important to understand how you should design and test these for them to be truly effective. With bigger large language models and tools such as retrieval-augmented generation (RAG) and Model Context Protocol (MCP) all the craze, it is important to understand that if a clear understanding of use case, design, and testing isn't done, these will likely become "black boxes." The goal of this article is to help a person decide what type of chatbot is really needed and how to go about thinking of design with a focus on performance, compliance, and the user's needs. It will briefly cover how to design and test effective chatbots, and future writings will dive deeper into chatbot design and testing. It would be helpful before reading this article to have a high-level understanding of LLMs, decision trees, RAG, and MCP. The Use Case Why Start With the Use Case To ensure you avoid finding a solution for a problem that was never present earlier, it is critical to understand the business goal and what the priorities are — is it speed, compliance, or customer satisfaction? A clear understanding of the use case helps decide what design and testing patterns are best to maximize the utility of the chatbot. The next section focuses on dividing use cases into three main general categories. Deterministic vs. Creative vs. Hybrid Model Once the goal is clear that there is a problem that needs to be solved using a chatbot, it is very likely that the use case lies on a spectrum ranging between highly deterministic tasks and highly creative tasks. A highly deterministic use case is one based on a largely fixed set of rules and predictable answers. A simple example would be asking a chatbot, "Do you have pencils available in the ABC store?" These answers are clear, well-defined, and repetitive. In fact, in some cases, an LLM may be harmful for this use case or unnecessarily overcomplicate it. A decision tree or rules engine may be the best model in this case. On the other end of the spectrum are the more creative tasks; an example of this would be a user asking a chatbot to "Help me come up with a creative marketing campaign for a new water bottle targeted at college students." While these are two extreme cases, most use cases fall under a hybrid path, which is partly deterministic and partly creative. An example would be a user saying, "I bought these pants and want to return them and buy something else." The deterministic part is fetching facts from a database or API and building a base for the LLM to refer to (known as grounding context) about the store's return policy and the day of the purchase. The creative part would be suggesting other options the user could buy. The image below shows the flow of how most use cases work. Designing the Chatbot Once the use case has been clearly determined, it is now time to design the chatbot. This will focus on choosing the right model, adding context, and manipulating parameters within an LLM. Choosing the Right Model Size Determining the right model is crucial to ensure that the use case is resolved in the most effective way at the lowest cost. Some factors involved are concurrency — how many users do you think will be using it at the same time? Latency — the time it takes for your chatbot to respond after a user sends a message. Complexity — are you resolving a problem that needs reasoning (more complex), or are you answering direct facts? Finally, the most important reason to choose the right model is to understand the associated costs of developing a chatbot. While there isn't a template of the right size, and I am not going to be choosing the model of any company, a smaller model for higher traffic and short factual tasks, and larger models for fewer users or complex reasoning queries tend to yield the best results. Adding Context Adding context is the process of bringing in factual data that the LLM uses to answer questions. This ensures an LLM doesn’t randomly give false information, known as hallucinations. Retrieval-augmented generation (RAG) is one method that ensures responses are grounded by fetching relevant facts from external sources. This helps give direction to a chatbot to provide the right answer as well as ensure that hallucinations don’t occur (especially in smaller models). Another newer method is the Model Context Protocol (MCPs). These allow an LLM to connect to multiple sources or APIs across different tools, allowing for context to be used dynamically based on the question asked of the LLM. It is good practice to move forward with the general assumption that end users are not prompt engineers. This means there shouldn't be any onus on them to be able to query in a certain way to get the answer they want. These processes allow for grounding and compliance requirements to be satisfied. Grounding refers to making your answer based on reliable data (your company’s database or a set of rules it is required to follow). This is critical in domains such as health, finance, and law. Parameter Tuning Manipulating parameters is another key factor in ensuring the chatbot provides the best results for your use case. The most common parameters to tune an LLM are listed below. The first parameter is temperature. The name was derived directly from the physics term "temperature," where a higher temperature corresponds to more randomness in particle movement. It ranges from 0 to 1, and internally it's used to scale the probability distribution of the LLM. In simple terms, a higher temperature (0.7-1) means a more randomized selection of words, whereas a lower temperature (0-0.2) would mean more deterministic, predictable answers. There is no hard and fast rule as to which is better. If the use case prioritizes factual QA or compliance, then a lower temperature should be set, but if the chatbot is used for brainstorming or ideation, then a higher temperature would be preferred. The next parameter is Top-p, which essentially focuses on how big a dataset (probability distribution) of words is considered when an LLM chooses the next word. Similar to temperature, low values (0.1-0.3) can be very restrictive, i.e., the results repeat themselves regularly, providing a predictable output. A higher value (0.8-1) provides a large sample set for the LLM to choose from. This means results are more creative and less predictable. The tradeoff is towards balancing diversity with coherence. The next are frequency and presence penalties; while these are tuned separately, they usually go hand in hand. Frequency penalty reduces the likelihood of the same word repeating multiple times, and presence encourages the model to introduce new words/concepts into the conversation. A high frequency and presence penalty together lead to output that is forced or sometimes incoherent. Finding the right balance between them is essential. Finally, max tokens: this focuses on the maximum length of your response. While deciding the number of tokens per response, assess the maximum cost you can spare, as more tokens mean a higher cost. More tokens also mean greater latency, i.e., the time taken to receive a response. Designing for Multiple Use Cases Many organizations use a chatbot for multiple use cases, so the question arises of how one adjusts all the above manipulations and designs to handle those. The simplest answer would be “an intent first” flow. What this means is to ask the user’s goal before starting the actual process. The easiest and most guaranteed way is to have an old-school if-else criterion. This usually works well if there are 4–5 well-defined use cases of your chatbot. The user would choose the use case they wanted, and then the parameters, model, and design can be adjusted accordingly. While this works for some cases, it would not work for chatbots with hundreds of different use cases. It could also be that the if-else criteria may seem too robotic and not bring in the human touch to the chatbot. The solution for this is similarity testing. The user enters their question; the chatbot then finds the most similar use case and adjusts the model and parameters dynamically. A more comprehensive article on designing for multiple use cases, I will write in the near future. Testing Testing your chatbot is an essential factor in ensuring your use case is being served as expected or better than expected. It is also necessary to see where it is going wrong and how things can be fixed. This section will focus on a brief overview of the different test metrics. Future readings will dive even deeper into testing and how exactly to perform them. The initial step is developing a gold standard dataset of the anticipated questions that could be asked. This could be past records or logs, usually answered by a human expert. As much as possible, it should also contain the expected answer. The gold dataset must also contain restricted questions that could possibly be asked that shouldn’t be answered. Running batch automated tests of these questions at least 1000 times and then scoring them based on precision, recall, accuracy, and latency is necessary. Based on your use case, testing for grounding and compliance may be required, too. Precision focuses on the number of correct answers given from the set of questions that are answered by the chatbot (ignoring those that it did not answer). Recall, on the other hand, indicates whether a bot misses an answer that is present in the gold dataset. For example, if there are 90 answers present in the knowledge base and the bot answers only 75 of them, then it missed 15 answers. Latency is the amount of time it generally takes for a chatbot to provide a response to a user's question. Three main values are used to understand latency P50 (median) – This is the time at which 50% of the queries are faster than it and 50% are slower than it. This typically indicates the general user experience.P90 (90th percentile latency) – This is the time at which 90% of the queries are faster than it and only 10% are slower. P99 (99th percentile latency) – This will be the time at which 99% of queries are faster than it, but 1% see it slower than this. This shows the possible worst-case latency. Grounding score is used to understand how many of the answers that have backed evidence utilize that backed evidence to answer the question. Compliance testing ensures that all restricted data or unethical questions are not answered by the chatbot at all Acceptance metrics are the thresholds set up for each of the above test parameters, based on which a chatbot can be deemed ready for deployment or requires further tuning or improvement. The flowchart below shows the overall design and testing that is done before deploying a chatbot into production. Even after it is deployed, it needs to be continuously tested and improved to ensure the chatbot is adjusted for changes and expectations of the real world. Live tracking of metrics such as latency, task completion, user satisfaction, compliance testing, and the number of escalations to a human being because the chatbot could not answer the question are just some of the metrics that need to be tested. Conclusion Chatbots, when designed well and tested properly, are a powerful tool that can resolve many use cases. Due to the ease and speed at which they can be built, sometimes compliance and clear design are ignored or not thoroughly looked into. However, for an effective chatbot, a systematic process of understanding the use case, designing based on the use case, and testing for that use case is an absolute necessity. In the future, I will deep dive separately in thorough detail into three main aspects, i.e, the design of a chatbot, the design of a multi-use case chatbot, and the testing of a chatbot.

By Vinayak Prasad

Culture and Methodologies

Agile

Agile

Career Development

Career Development

Methodologies

Methodologies

Team Management

Team Management

Long-Running Durable Agents With Spring AI and Dapr Workflows

October 10, 2025 by Mauricio Salatino

Golden Paths in IDPs: From Developer Chaos to Clarity

October 8, 2025 by Josephine Eskaline Joyce DZone Core CORE

The Agile AI Manifesto

October 8, 2025 by Stefan Wolpers DZone Core CORE

Data Engineering

AI/ML

AI/ML

Big Data

Big Data

Databases

Databases

IoT

IoT

Infusing AI into Your Java Applications

October 10, 2025 by Don Bourne

Long-Running Durable Agents With Spring AI and Dapr Workflows

October 10, 2025 by Mauricio Salatino

Introduction to Spring Data Elasticsearch 5.5

October 10, 2025 by Arnošt Havelka DZone Core CORE

Software Design and Architecture

Cloud Architecture

Cloud Architecture

Integration

Integration

Microservices

Microservices

Performance

Performance

Long-Running Durable Agents With Spring AI and Dapr Workflows

October 10, 2025 by Mauricio Salatino

Fixing a Test Involves Much More Than Simply Making It Pass

October 10, 2025 by Stelios Manioudakis, PhD DZone Core CORE

AI-Assisted Kubernetes Diagnostics: A Practical Implementation

October 10, 2025 by Shamsher Khan

Coding

Frameworks

Frameworks

Java

Java

JavaScript

JavaScript

Languages

Languages

Tools

Tools

Infusing AI into Your Java Applications

October 10, 2025 by Don Bourne

Diving into JNI: My Messy Adventures With C++ in Android

October 10, 2025 by Ruslan Vidzert

Long-Running Durable Agents With Spring AI and Dapr Workflows

October 10, 2025 by Mauricio Salatino

Testing, Deployment, and Maintenance

Deployment

Deployment

DevOps and CI/CD

DevOps and CI/CD

Maintenance

Maintenance

Monitoring and Observability

Monitoring and Observability

Diving into JNI: My Messy Adventures With C++ in Android

October 10, 2025 by Ruslan Vidzert

Stop React Native Crashes: A Production-Ready Error Handling Guide

October 10, 2025 by Anujkumarsinh Donvir DZone Core CORE

Fixing a Test Involves Much More Than Simply Making It Pass

October 10, 2025 by Stelios Manioudakis, PhD DZone Core CORE

Popular

AI/ML

AI/ML

Java

Java

JavaScript

JavaScript

Open Source

Open Source

Infusing AI into Your Java Applications

October 10, 2025 by Don Bourne

Diving into JNI: My Messy Adventures With C++ in Android

October 10, 2025 by Ruslan Vidzert

Long-Running Durable Agents With Spring AI and Dapr Workflows

October 10, 2025 by Mauricio Salatino

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: